From 96d6b309ec394dafb98ab944c842fda905af649e Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Wed, 13 Apr 2022 19:41:07 +0800
Subject: [PATCH 001/141] Migrate database

---
 .../6.json                                    | 737 ++++++++++++++++++
 .../newpipe/database/DatabaseMigrationTest.kt |   5 +
 .../org/schabi/newpipe/NewPipeDatabase.java   |   4 +-
 .../schabi/newpipe/database/AppDatabase.java  |   4 +-
 .../schabi/newpipe/database/Migrations.java   |  42 +-
 .../playlist/model/PlaylistEntity.java        |  12 +
 6 files changed, 800 insertions(+), 4 deletions(-)
 create mode 100644 app/schemas/org.schabi.newpipe.database.AppDatabase/6.json

diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json
new file mode 100644
index 000000000..34d457f83
--- /dev/null
+++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json
@@ -0,0 +1,737 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 6,
+    "identityHash": "cc9c4d84f52f49105b1c4216b948b5f7",
+    "entities": [
+      {
+        "tableName": "subscriptions",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "avatarUrl",
+            "columnName": "avatar_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "subscriberCount",
+            "columnName": "subscriber_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "notificationMode",
+            "columnName": "notification_mode",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "uid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_subscriptions_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "search_history",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "creationDate",
+            "columnName": "creation_date",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "search",
+            "columnName": "search",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_search_history_search",
+            "unique": false,
+            "columnNames": [
+              "search"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "streams",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "streamType",
+            "columnName": "stream_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "duration",
+            "columnName": "duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uploader",
+            "columnName": "uploader",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uploaderUrl",
+            "columnName": "uploader_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnail_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "viewCount",
+            "columnName": "view_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "textualUploadDate",
+            "columnName": "textual_upload_date",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "uploadDate",
+            "columnName": "upload_date",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "isUploadDateApproximation",
+            "columnName": "is_upload_date_approximation",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "uid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_streams_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "stream_history",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accessDate",
+            "columnName": "access_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "repeatCount",
+            "columnName": "repeat_count",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "stream_id",
+            "access_date"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_stream_history_stream_id",
+            "unique": false,
+            "columnNames": [
+              "stream_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "stream_state",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "progressMillis",
+            "columnName": "progress_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "stream_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "playlists",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT, `display_index` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnail_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "displayIndex",
+            "columnName": "display_index",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "uid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_playlists_name",
+            "unique": false,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "playlist_stream_join",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "playlistUid",
+            "columnName": "playlist_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "index",
+            "columnName": "join_index",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "playlist_id",
+            "join_index"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_playlist_stream_join_playlist_id_join_index",
+            "unique": true,
+            "columnNames": [
+              "playlist_id",
+              "join_index"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
+          },
+          {
+            "name": "index_playlist_stream_join_stream_id",
+            "unique": false,
+            "columnNames": [
+              "stream_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "playlists",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "playlist_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "remote_playlists",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnail_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "uploader",
+            "columnName": "uploader",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "streamCount",
+            "columnName": "stream_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "uid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_remote_playlists_name",
+            "unique": false,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
+          },
+          {
+            "name": "index_remote_playlists_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "feed",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "streamId",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "stream_id",
+            "subscription_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_feed_subscription_id",
+            "unique": false,
+            "columnNames": [
+              "subscription_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "feed_group",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "icon",
+            "columnName": "icon_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sortOrder",
+            "columnName": "sort_order",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "uid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_feed_group_sort_order",
+            "unique": false,
+            "columnNames": [
+              "sort_order"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "feed_group_subscription_join",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "feedGroupId",
+            "columnName": "group_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "group_id",
+            "subscription_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_feed_group_subscription_join_subscription_id",
+            "unique": false,
+            "columnNames": [
+              "subscription_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "feed_group",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "group_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "feed_last_updated",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastUpdated",
+            "columnName": "last_updated",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "subscription_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cc9c4d84f52f49105b1c4216b948b5f7')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index 28dea13e9..6d05a45bf 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -84,6 +84,11 @@ class DatabaseMigrationTest {
             true, Migrations.MIGRATION_4_5
         )
 
+        testHelper.runMigrationsAndValidate(
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
+            true, Migrations.MIGRATION_5_6
+        )
+
         val migratedDatabaseV3 = getMigratedDatabase()
         val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
 
diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
index 402d4648d..fc3423994 100644
--- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
@@ -5,6 +5,7 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5;
+import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6;
 
 import android.content.Context;
 import android.database.Cursor;
@@ -24,7 +25,8 @@ public final class NewPipeDatabase {
     private static AppDatabase getDatabase(final Context context) {
         return Room
                 .databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
-                .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
+                .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
+                        MIGRATION_5_6)
                 .build();
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
index 28ddc8184..563e80b17 100644
--- a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
@@ -1,6 +1,6 @@
 package org.schabi.newpipe.database;
 
-import static org.schabi.newpipe.database.Migrations.DB_VER_5;
+import static org.schabi.newpipe.database.Migrations.DB_VER_6;
 
 import androidx.room.Database;
 import androidx.room.RoomDatabase;
@@ -38,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
                 FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
                 FeedLastUpdatedEntity.class
         },
-        version = DB_VER_5
+        version = DB_VER_6
 )
 public abstract class AppDatabase extends RoomDatabase {
     public static final String DATABASE_NAME = "newpipe.db";
diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index 7de08442c..a8f093ba0 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -23,6 +23,7 @@ public final class Migrations {
     public static final int DB_VER_3 = 3;
     public static final int DB_VER_4 = 4;
     public static final int DB_VER_5 = 5;
+    public static final int DB_VER_6 = 6;
 
     private static final String TAG = Migrations.class.getName();
     public static final boolean DEBUG = MainActivity.DEBUG;
@@ -184,7 +185,46 @@ public final class Migrations {
         @Override
         public void migrate(@NonNull final SupportSQLiteDatabase database) {
             database.execSQL("ALTER TABLE `subscriptions` ADD COLUMN `notification_mode` "
-                     + "INTEGER NOT NULL DEFAULT 0");
+                    + "INTEGER NOT NULL DEFAULT 0");
+        }
+    };
+
+    public static final Migration MIGRATION_5_6 = new Migration(DB_VER_5, DB_VER_6) {
+        @Override
+        public void migrate(@NonNull final SupportSQLiteDatabase database) {
+            try {
+                database.beginTransaction();
+
+                // create a temp table to initialize display_index
+                database.execSQL("CREATE TABLE `playlists_tmp` "
+                        + "(`uid` INTEGER NOT NULL, "
+                        + "`name` TEXT, `thumbnail_url` TEXT,"
+                        + "`display_index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)");
+                database.execSQL("INSERT INTO `playlists_tmp` (`uid`, `name`, `thumbnail_url`)"
+                        + "SELECT `uid`, `name`, `thumbnail_url` FROM `playlists`");
+
+                // drop the old table and create new one
+                database.execSQL("DROP TABLE `playlists`");
+                database.execSQL("CREATE TABLE `playlists` "
+                        + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+                        + "`name` TEXT, `thumbnail_url` TEXT,"
+                        + "`display_index` INTEGER NOT NULL UNIQUE)");
+
+                // insert temp data into the new table
+                // set display_index start from zero
+                database.execSQL("INSERT INTO `playlists` SELECT * FROM `playlists_tmp`");
+                database.execSQL("UPDATE `playlists` SET `display_index` = `display_index` - 1");
+
+                // drop tmp table
+                database.execSQL("DROP TABLE `playlists_tmp`");
+
+                // create index on the new table
+                database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
+
+                database.setTransactionSuccessful();
+            } finally {
+                database.endTransaction();
+            }
         }
     };
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index 71abf2732..c1ae0a2b3 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -15,6 +15,7 @@ public class PlaylistEntity {
     public static final String PLAYLIST_ID = "uid";
     public static final String PLAYLIST_NAME = "name";
     public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
+    public static final String PLAYLIST_DISPLAY_INDEX = "display_index";
 
     @PrimaryKey(autoGenerate = true)
     @ColumnInfo(name = PLAYLIST_ID)
@@ -26,6 +27,9 @@ public class PlaylistEntity {
     @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
     private String thumbnailUrl;
 
+    @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
+    private long displayIndex = 0;
+
     public PlaylistEntity(final String name, final String thumbnailUrl) {
         this.name = name;
         this.thumbnailUrl = thumbnailUrl;
@@ -54,4 +58,12 @@ public class PlaylistEntity {
     public void setThumbnailUrl(final String thumbnailUrl) {
         this.thumbnailUrl = thumbnailUrl;
     }
+
+    public long getDisplayIndex() {
+        return displayIndex;
+    }
+
+    public void setDisplayIndex(final long displayIndex) {
+        this.displayIndex = displayIndex;
+    }
 }

From c34549a47d0c39050ddd34357e43adfce56ea638 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Wed, 13 Apr 2022 21:35:38 +0800
Subject: [PATCH 002/141] Update database migrations and getter/setter

---
 .../6.json                                    | 12 +++--
 .../schabi/newpipe/database/Migrations.java   | 44 ++++++++++++-------
 .../database/playlist/PlaylistLocalItem.java  |  1 +
 .../playlist/PlaylistMetadataEntry.java       |  6 ++-
 .../playlist/dao/PlaylistStreamDAO.java       |  2 +
 .../playlist/model/PlaylistRemoteEntity.java  | 25 +++++++++++
 .../local/bookmark/BookmarkFragment.java      |  1 -
 .../local/playlist/LocalPlaylistManager.java  | 15 +++++--
 8 files changed, 83 insertions(+), 23 deletions(-)

diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json
index 34d457f83..3ef363e30 100644
--- a/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json
+++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/6.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 6,
-    "identityHash": "cc9c4d84f52f49105b1c4216b948b5f7",
+    "identityHash": "9ffc14521c566beed378d77430de3f0c",
     "entities": [
       {
         "tableName": "subscriptions",
@@ -447,7 +447,7 @@
       },
       {
         "tableName": "remote_playlists",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `display_index` INTEGER NOT NULL, `stream_count` INTEGER)",
         "fields": [
           {
             "fieldPath": "uid",
@@ -485,6 +485,12 @@
             "affinity": "TEXT",
             "notNull": false
           },
+          {
+            "fieldPath": "displayIndex",
+            "columnName": "display_index",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
           {
             "fieldPath": "streamCount",
             "columnName": "stream_count",
@@ -731,7 +737,7 @@
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cc9c4d84f52f49105b1c4216b948b5f7')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9ffc14521c566beed378d77430de3f0c')"
     ]
   }
 }
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index a8f093ba0..ffca6cca5 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -195,32 +195,46 @@ public final class Migrations {
             try {
                 database.beginTransaction();
 
+                // update playlists
                 // create a temp table to initialize display_index
                 database.execSQL("CREATE TABLE `playlists_tmp` "
-                        + "(`uid` INTEGER NOT NULL, "
+                        + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                         + "`name` TEXT, `thumbnail_url` TEXT,"
-                        + "`display_index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)");
+                        + "`display_index` INTEGER NOT NULL DEFAULT 0)");
                 database.execSQL("INSERT INTO `playlists_tmp` (`uid`, `name`, `thumbnail_url`)"
                         + "SELECT `uid`, `name`, `thumbnail_url` FROM `playlists`");
 
-                // drop the old table and create new one
+                // replace the old table
                 database.execSQL("DROP TABLE `playlists`");
-                database.execSQL("CREATE TABLE `playlists` "
-                        + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
-                        + "`name` TEXT, `thumbnail_url` TEXT,"
-                        + "`display_index` INTEGER NOT NULL UNIQUE)");
-
-                // insert temp data into the new table
-                // set display_index start from zero
-                database.execSQL("INSERT INTO `playlists` SELECT * FROM `playlists_tmp`");
-                database.execSQL("UPDATE `playlists` SET `display_index` = `display_index` - 1");
-
-                // drop tmp table
-                database.execSQL("DROP TABLE `playlists_tmp`");
+                database.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`");
 
                 // create index on the new table
                 database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
 
+
+                // update remote_playlists
+                // create a temp table to initialize display_index
+                database.execSQL("CREATE TABLE `remote_playlists_tmp` "
+                        + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+                        + "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
+                        + "`thumbnail_url` TEXT, `uploader` TEXT, "
+                        + "`display_index` INTEGER NOT NULL DEFAULT 0,"
+                        + "`stream_count` INTEGER)");
+                database.execSQL("INSERT INTO `remote_playlists_tmp` (`uid`, `service_id`, "
+                        + "`name`, `url`, `thumbnail_url`, `uploader`, `stream_count`)"
+                        + "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
+                        + "`stream_count` FROM `remote_playlists`");
+
+                // replace the old table
+                database.execSQL("DROP TABLE `remote_playlists`");
+                database.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`");
+
+                // create index on the new table
+                database.execSQL("CREATE INDEX `index_remote_playlists_name` "
+                        + "ON `remote_playlists` (`name`)");
+                database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
+                        + "ON `remote_playlists` (`service_id`, `url`)");
+
                 database.setTransactionSuccessful();
             } finally {
                 database.endTransaction();
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 43dbd89ea..bc17c51c4 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -14,6 +14,7 @@ public interface PlaylistLocalItem extends LocalItem {
     static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
+        // todo: merge algorithm
         final List<PlaylistLocalItem> items = new ArrayList<>(
                 localPlaylists.size() + remotePlaylists.size());
         items.addAll(localPlaylists);
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
index a13894030..29bad45dc 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist;
 
 import androidx.room.ColumnInfo;
 
+import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
@@ -15,14 +16,17 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
     public final String name;
     @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
     public final String thumbnailUrl;
+    @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
+    public final long displayIndex;
     @ColumnInfo(name = PLAYLIST_STREAM_COUNT)
     public final long streamCount;
 
     public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl,
-                                 final long streamCount) {
+                                 final long displayIndex, final long streamCount) {
         this.uid = uid;
         this.name = name;
         this.thumbnailUrl = thumbnailUrl;
+        this.displayIndex = displayIndex;
         this.streamCount = streamCount;
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
index 4941d9395..3fb96a21f 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
@@ -15,6 +15,7 @@ import java.util.List;
 import io.reactivex.rxjava3.core.Flowable;
 
 import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
+import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
@@ -75,6 +76,7 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
 
     @Transaction
     @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
+            + PLAYLIST_DISPLAY_INDEX + ", "
             + "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
 
             + " FROM " + PLAYLIST_TABLE
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
index 2e9a15d7d..1fddfa732 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
@@ -31,6 +31,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
     public static final String REMOTE_PLAYLIST_URL = "url";
     public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
     public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
+    public static final String REMOTE_PLAYLIST_DISPLAY_INDEX = "display_index";
     public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
 
     @PrimaryKey(autoGenerate = true)
@@ -52,6 +53,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
     @ColumnInfo(name = REMOTE_PLAYLIST_UPLOADER_NAME)
     private String uploader;
 
+    @ColumnInfo(name = REMOTE_PLAYLIST_DISPLAY_INDEX)
+    private long displayIndex;
+
     @ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
     private Long streamCount;
 
@@ -66,6 +70,19 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
         this.streamCount = streamCount;
     }
 
+    @Ignore
+    public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
+                                final String thumbnailUrl, final String uploader,
+                                final long displayIndex, final Long streamCount) {
+        this.serviceId = serviceId;
+        this.name = name;
+        this.url = url;
+        this.thumbnailUrl = thumbnailUrl;
+        this.uploader = uploader;
+        this.displayIndex = displayIndex;
+        this.streamCount = streamCount;
+    }
+
     @Ignore
     public PlaylistRemoteEntity(final PlaylistInfo info) {
         this(info.getServiceId(), info.getName(), info.getUrl(),
@@ -136,6 +153,14 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
         this.uploader = uploader;
     }
 
+    public long getDisplayIndex() {
+        return displayIndex;
+    }
+
+    public void setDisplayIndex(final long displayIndex) {
+        this.displayIndex = displayIndex;
+    }
+
     public Long getStreamCount() {
         return streamCount;
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index f272a8831..2f36cbd55 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -308,7 +308,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                     + "with new name=[" + name + "] items");
         }
 
-        localPlaylistManager.renamePlaylist(id, name);
         final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
index 33296aa84..aabda1bf0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
@@ -96,12 +96,17 @@ public class LocalPlaylistManager {
     }
 
     public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
-        return modifyPlaylist(playlistId, name, null);
+        return modifyPlaylist(playlistId, name, null, -1);
     }
 
     public Maybe<Integer> changePlaylistThumbnail(final long playlistId,
                                                   final String thumbnailUrl) {
-        return modifyPlaylist(playlistId, null, thumbnailUrl);
+        return modifyPlaylist(playlistId, null, thumbnailUrl, -1);
+    }
+
+    public Maybe<Integer> changePlaylistDisplayIndex(final long playlistId,
+                                                     final long displayIndex) {
+        return modifyPlaylist(playlistId, null, null, displayIndex);
     }
 
     public String getPlaylistThumbnail(final long playlistId) {
@@ -110,7 +115,8 @@ public class LocalPlaylistManager {
 
     private Maybe<Integer> modifyPlaylist(final long playlistId,
                                           @Nullable final String name,
-                                          @Nullable final String thumbnailUrl) {
+                                          @Nullable final String thumbnailUrl,
+                                          final long displayIndex) {
         return playlistTable.getPlaylist(playlistId)
                 .firstElement()
                 .filter(playlistEntities -> !playlistEntities.isEmpty())
@@ -122,6 +128,9 @@ public class LocalPlaylistManager {
                     if (thumbnailUrl != null) {
                         playlist.setThumbnailUrl(thumbnailUrl);
                     }
+                    if (displayIndex != -1) {
+                        playlist.setDisplayIndex(displayIndex);
+                    }
                     return playlistTable.update(playlist);
                 }).subscribeOn(Schedulers.io());
     }

From 270a541a7c98e975d6c848fce9ee232b6620432b Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Wed, 13 Apr 2022 22:46:24 +0800
Subject: [PATCH 003/141] Implement algorithm to merge playlists

---
 .../database/playlist/PlaylistLocalItem.java  | 58 ++++++++++++++++---
 .../playlist/PlaylistMetadataEntry.java       |  5 ++
 .../playlist/model/PlaylistRemoteEntity.java  |  1 +
 3 files changed, 57 insertions(+), 7 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index bc17c51c4..ae81ce3f5 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -11,18 +11,62 @@ import java.util.List;
 public interface PlaylistLocalItem extends LocalItem {
     String getOrderingName();
 
+    long getDisplayIndex();
+
     static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
-        // todo: merge algorithm
-        final List<PlaylistLocalItem> items = new ArrayList<>(
+
+        // Merge localPlaylists and remotePlaylists by displayIndex.
+        // If two items have the same displayIndex, sort them in CASE_INSENSITIVE_ORDER.
+        // This algorithm is similar to the merge operation in merge sort.
+
+        final List<PlaylistLocalItem> result = new ArrayList<>(
                 localPlaylists.size() + remotePlaylists.size());
-        items.addAll(localPlaylists);
-        items.addAll(remotePlaylists);
+        final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
+        int i = 0;
+        int j = 0;
+        while (i < localPlaylists.size()) {
+            while (j < remotePlaylists.size()) {
+                if (remotePlaylists.get(j).getDisplayIndex()
+                        <= localPlaylists.get(i).getDisplayIndex()) {
+                    addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
+                    j++;
+                } else {
+                    break;
+                }
+            }
+            addItem(result, localPlaylists.get(i), itemsWithSameIndex);
+            i++;
+        }
+        addItemsWithSameIndex(result, itemsWithSameIndex);
 
-        Collections.sort(items, Comparator.comparing(PlaylistLocalItem::getOrderingName,
-                Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
+        // If displayIndex does not match actual index, update displayIndex.
+        // This may happen when a new list is created with default displayIndex = 0.
+        // todo: update displayIndex
 
-        return items;
+        return result;
+    }
+
+    static void addItem(final List<PlaylistLocalItem> result, final PlaylistLocalItem item,
+                        final List<PlaylistLocalItem> itemsWithSameIndex) {
+        if (!itemsWithSameIndex.isEmpty()
+                && itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
+            // The new item has a different displayIndex,
+            // add previous items with same index to the result.
+            addItemsWithSameIndex(result, itemsWithSameIndex);
+            itemsWithSameIndex.clear();
+        }
+        itemsWithSameIndex.add(item);
+    }
+
+    static void addItemsWithSameIndex(final List<PlaylistLocalItem> result,
+                                      final List<PlaylistLocalItem> itemsWithSameIndex) {
+        if (itemsWithSameIndex.size() > 1) {
+            Collections.sort(itemsWithSameIndex,
+                    Comparator.comparing(PlaylistLocalItem::getOrderingName,
+                            Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
+        }
+        result.addAll(itemsWithSameIndex);
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
index 29bad45dc..f54ffff13 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
@@ -39,4 +39,9 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
     public String getOrderingName() {
         return name;
     }
+
+    @Override
+    public long getDisplayIndex() {
+        return displayIndex;
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
index 1fddfa732..454526769 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
@@ -153,6 +153,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
         this.uploader = uploader;
     }
 
+    @Override
     public long getDisplayIndex() {
         return displayIndex;
     }

From ba8370bcfd980089ef5984b6ca55e4b072a69fee Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 14 Apr 2022 12:13:42 +0800
Subject: [PATCH 004/141] Save changes to the database and bugfix

---
 .../database/playlist/PlaylistLocalItem.java  | 14 ++--
 .../playlist/dao/PlaylistRemoteDAO.java       |  4 ++
 .../playlist/model/PlaylistEntity.java        |  6 +-
 .../newpipe/local/LocalItemListAdapter.java   |  1 +
 .../local/bookmark/BookmarkFragment.java      | 65 ++++++++++++++++++-
 .../local/playlist/LocalPlaylistManager.java  |  5 +-
 .../local/playlist/RemotePlaylistManager.java | 15 +++++
 7 files changed, 100 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index ae81ce3f5..5bf50cd97 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -24,6 +24,12 @@ public interface PlaylistLocalItem extends LocalItem {
         final List<PlaylistLocalItem> result = new ArrayList<>(
                 localPlaylists.size() + remotePlaylists.size());
         final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
+
+        // The data from database may not be in the displayIndex order
+        Collections.sort(localPlaylists,
+                Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
+        Collections.sort(remotePlaylists,
+                Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
         int i = 0;
         int j = 0;
         while (i < localPlaylists.size()) {
@@ -41,10 +47,6 @@ public interface PlaylistLocalItem extends LocalItem {
         }
         addItemsWithSameIndex(result, itemsWithSameIndex);
 
-        // If displayIndex does not match actual index, update displayIndex.
-        // This may happen when a new list is created with default displayIndex = 0.
-        // todo: update displayIndex
-
         return result;
     }
 
@@ -52,8 +54,8 @@ public interface PlaylistLocalItem extends LocalItem {
                         final List<PlaylistLocalItem> itemsWithSameIndex) {
         if (!itemsWithSameIndex.isEmpty()
                 && itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
-            // The new item has a different displayIndex,
-            // add previous items with same index to the result.
+            // The new item has a different displayIndex, add previous items with same
+            // index to the result.
             addItemsWithSameIndex(result, itemsWithSameIndex);
             itemsWithSameIndex.clear();
         }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
index 6bb849428..ade857464 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
@@ -31,6 +31,10 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
             + " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
     Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
 
+    @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+            + REMOTE_PLAYLIST_ID + " = :playlistId")
+    Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
+
     @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
             + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
     Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index c1ae0a2b3..82f697ab3 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.model;
 
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
+import androidx.room.Ignore;
 import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
@@ -28,11 +29,12 @@ public class PlaylistEntity {
     private String thumbnailUrl;
 
     @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
-    private long displayIndex = 0;
+    private long displayIndex;
 
-    public PlaylistEntity(final String name, final String thumbnailUrl) {
+    public PlaylistEntity(final String name, final String thumbnailUrl, final long displayIndex) {
         this.name = name;
         this.thumbnailUrl = thumbnailUrl;
+        this.displayIndex = displayIndex;
     }
 
     public long getUid() {
diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index 05e2fdac0..e2bfd5977 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -142,6 +142,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
     }
 
     public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
+        // todo: reuse this code?
         final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
         final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 2f36cbd55..6d2fbfbfc 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -199,6 +199,13 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
             @Override
             public void onNext(final List<PlaylistLocalItem> subscriptions) {
+
+                // If displayIndex does not match actual index, update displayIndex.
+                // This may happen when a new list is created
+                // or on the first run after database update
+                // or displayIndex is not continuous for some reason.
+                checkDisplayIndexUpdate(subscriptions);
+
                 handleResult(subscriptions);
                 if (databaseSubscription != null) {
                     databaseSubscription.request(1);
@@ -212,7 +219,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             }
 
             @Override
-            public void onComplete() { }
+            public void onComplete() {
+            }
         };
     }
 
@@ -316,5 +324,60 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                                 "Changing playlist name")));
         disposables.add(disposable);
     }
+
+    private void changeLocalPlaylistDisplayIndex(final long id, final long displayIndex) {
+
+        if (localPlaylistManager == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Updating local playlist id=[" + id + "] "
+                    + "with new display_index=[" + displayIndex + "]");
+        }
+
+        final Disposable disposable =
+                localPlaylistManager.changePlaylistDisplayIndex(id, displayIndex)
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
+                                new ErrorInfo(throwable,
+                                        UserAction.REQUESTED_BOOKMARK,
+                                        "Changing local playlist display_index")));
+        disposables.add(disposable);
+    }
+
+    private void changeRemotePlaylistDisplayIndex(final long id, final long displayIndex) {
+
+        if (remotePlaylistManager == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Updating remote playlist id=[" + id + "] "
+                    + "with new display_index=[" + displayIndex + "]");
+        }
+
+        final Disposable disposable =
+                remotePlaylistManager.changePlaylistDisplayIndex(id, displayIndex)
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
+                                new ErrorInfo(throwable,
+                                        UserAction.REQUESTED_BOOKMARK,
+                                        "Changing remote playlist display_index")));
+        disposables.add(disposable);
+    }
+
+    private void checkDisplayIndexUpdate(@NonNull final List<PlaylistLocalItem> result) {
+        for (int i = 0; i < result.size(); i++) {
+            final PlaylistLocalItem item = result.get(i);
+            if (item.getDisplayIndex() != i) {
+                if (item instanceof PlaylistMetadataEntry) {
+                    changeLocalPlaylistDisplayIndex(((PlaylistMetadataEntry) item).uid, i);
+                } else if (item instanceof PlaylistRemoteEntity) {
+                    changeRemotePlaylistDisplayIndex(((PlaylistRemoteEntity) item).getUid(), i);
+                }
+            }
+        }
+    }
 }
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
index aabda1bf0..47817f9e4 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
@@ -40,8 +40,11 @@ public class LocalPlaylistManager {
             return Maybe.empty();
         }
         final StreamEntity defaultStream = streams.get(0);
+
+        // Make sure the new playlist is always on the top of bookmark.
+        // The index will be reassigned to non-negative number in BookmarkFragment.
         final PlaylistEntity newPlaylist =
-                new PlaylistEntity(name, defaultStream.getThumbnailUrl());
+                new PlaylistEntity(name, defaultStream.getThumbnailUrl(), -1);
 
         return Maybe.fromCallable(() -> database.runInTransaction(() ->
                 upsertStreams(playlistTable.insert(newPlaylist), streams, 0))
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
index 5221139e3..b49f149d6 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
@@ -8,6 +8,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
 import java.util.List;
 
 import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.Maybe;
 import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
@@ -33,6 +34,20 @@ public class RemotePlaylistManager {
                 .subscribeOn(Schedulers.io());
     }
 
+    public Maybe<Integer> changePlaylistDisplayIndex(final long playlistId,
+                                                     final long displayIndex) {
+        return playlistRemoteTable.getPlaylist(playlistId)
+                .firstElement()
+                .filter(playlistRemoteEntities -> !playlistRemoteEntities.isEmpty())
+                .map(playlistRemoteEntities -> {
+                    final PlaylistRemoteEntity playlist = playlistRemoteEntities.get(0);
+                    if (displayIndex != -1) {
+                        playlist.setDisplayIndex(displayIndex);
+                    }
+                    return playlistRemoteTable.update(playlist);
+                }).subscribeOn(Schedulers.io());
+    }
+
     public Single<Long> onBookmark(final PlaylistInfo playlistInfo) {
         return Single.fromCallable(() -> {
             final PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);

From bfb56b4144f1fec9680dbafd5fe79420d731e0a7 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 14 Apr 2022 16:59:52 +0800
Subject: [PATCH 005/141] UI design and behavior

---
 .../playlist/model/PlaylistEntity.java        |   1 -
 .../local/bookmark/BookmarkFragment.java      | 207 +++++++++++++-----
 .../local/holder/LocalPlaylistItemHolder.java |  21 +-
 .../holder/RemotePlaylistItemHolder.java      |  22 +-
 .../layout/list_playlist_bookmark_item.xml    |  84 +++++++
 5 files changed, 282 insertions(+), 53 deletions(-)
 create mode 100644 app/src/main/res/layout/list_playlist_bookmark_item.xml

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index 82f697ab3..cdbbdebc0 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -2,7 +2,6 @@ package org.schabi.newpipe.database.playlist.model;
 
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
-import androidx.room.Ignore;
 import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 6d2fbfbfc..63c63c1e0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -1,5 +1,7 @@
 package org.schabi.newpipe.local.bookmark;
 
+import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
+
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.InputType;
@@ -12,6 +14,8 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
 
 import org.reactivestreams.Subscriber;
 import org.reactivestreams.Subscription;
@@ -41,6 +45,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import io.reactivex.rxjava3.disposables.Disposable;
 
 public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
+    private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
     protected Parcelable itemsListState;
 
@@ -48,6 +53,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     private CompositeDisposable disposables = new CompositeDisposable();
     private LocalPlaylistManager localPlaylistManager;
     private RemotePlaylistManager remotePlaylistManager;
+    private ItemTouchHelper itemTouchHelper;
 
     ///////////////////////////////////////////////////////////////////////////
     // Fragment LifeCycle - Creation
@@ -98,6 +104,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     protected void initListeners() {
         super.initListeners();
 
+        itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
+        itemTouchHelper.attachToRecyclerView(itemsList);
+
         itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
             @Override
             public void selected(final LocalItem selectedItem) {
@@ -126,6 +135,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                     showRemoteDeleteDialog((PlaylistRemoteEntity) selectedItem);
                 }
             }
+
+            @Override
+            public void drag(final LocalItem selectedItem,
+                             final RecyclerView.ViewHolder viewHolder) {
+                if (itemTouchHelper != null) {
+                    itemTouchHelper.startDrag(viewHolder);
+                }
+            }
         });
     }
 
@@ -166,6 +183,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
 
         databaseSubscription = null;
+        itemTouchHelper = null;
     }
 
     @Override
@@ -255,56 +273,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // Utils
-    ///////////////////////////////////////////////////////////////////////////
-
-    private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
-        showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
-    }
-
-    private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
-        final DialogEditTextBinding dialogBinding
-                = DialogEditTextBinding.inflate(getLayoutInflater());
-        dialogBinding.dialogEditText.setHint(R.string.name);
-        dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
-        dialogBinding.dialogEditText.setText(selectedItem.name);
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
-        builder.setView(dialogBinding.getRoot())
-                .setPositiveButton(R.string.rename_playlist, (dialog, which) ->
-                        changeLocalPlaylistName(
-                                selectedItem.uid,
-                                dialogBinding.dialogEditText.getText().toString()))
-                .setNegativeButton(R.string.cancel, null)
-                .setNeutralButton(R.string.delete, (dialog, which) -> {
-                    showDeleteDialog(selectedItem.name,
-                            localPlaylistManager.deletePlaylist(selectedItem.uid));
-                    dialog.dismiss();
-                })
-                .create()
-                .show();
-    }
-
-    private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
-        if (activity == null || disposables == null) {
-            return;
-        }
-
-        new AlertDialog.Builder(activity)
-                .setTitle(name)
-                .setMessage(R.string.delete_playlist_prompt)
-                .setCancelable(true)
-                .setPositiveButton(R.string.delete, (dialog, i) ->
-                        disposables.add(deleteReactor
-                                .observeOn(AndroidSchedulers.mainThread())
-                                .subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
-                                        showError(new ErrorInfo(throwable,
-                                                UserAction.REQUESTED_BOOKMARK,
-                                                "Deleting playlist")))))
-                .setNegativeButton(R.string.cancel, null)
-                .show();
-    }
+    /*//////////////////////////////////////////////////////////////////////////
+    // Playlist Metadata Manipulation
+    //////////////////////////////////////////////////////////////////////////*/
 
     private void changeLocalPlaylistName(final long id, final String name) {
         if (localPlaylistManager == null) {
@@ -379,5 +350,141 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             }
         }
     }
+
+    private void saveImmediate() {
+        if (localPlaylistManager == null || remotePlaylistManager == null
+                || itemListAdapter == null) {
+            return;
+        }
+        // todo: debounce
+        /*
+        // List must be loaded and modified in order to save
+        if (isLoadingComplete == null || isModified == null
+                || !isLoadingComplete.get() || !isModified.get()) {
+            Log.w(TAG, "Attempting to save playlist when local playlist "
+                    + "is not loaded or not modified: playlist id=[" + playlistId + "]");
+            return;
+        }
+        */
+        // todo: is it correct?
+        final List<LocalItem> items = itemListAdapter.getItemsList();
+        for (int i = 0; i < items.size(); i++) {
+            final LocalItem item = items.get(i);
+            if (item instanceof PlaylistMetadataEntry) {
+                changeLocalPlaylistDisplayIndex(((PlaylistMetadataEntry) item).uid, i);
+            } else if (item instanceof PlaylistRemoteEntity) {
+                changeLocalPlaylistDisplayIndex(((PlaylistRemoteEntity) item).getUid(), i);
+            }
+        }
+    }
+
+    private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
+        int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+        if (shouldUseGridLayout(requireContext())) {
+            directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
+        }
+        return new ItemTouchHelper.SimpleCallback(directions,
+                ItemTouchHelper.ACTION_STATE_IDLE) {
+            @Override
+            public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,
+                                                    final int viewSize,
+                                                    final int viewSizeOutOfBounds,
+                                                    final int totalSize,
+                                                    final long msSinceStartScroll) {
+                final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView,
+                        viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll);
+                final int minimumAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY,
+                        Math.abs(standardSpeed));
+                return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
+            }
+
+            @Override
+            public boolean onMove(@NonNull final RecyclerView recyclerView,
+                                  @NonNull final RecyclerView.ViewHolder source,
+                                  @NonNull final RecyclerView.ViewHolder target) {
+                if (source.getItemViewType() != target.getItemViewType()
+                        || itemListAdapter == null) {
+                    return false;
+                }
+
+                // todo: is it correct
+                final int sourceIndex = source.getBindingAdapterPosition();
+                final int targetIndex = target.getBindingAdapterPosition();
+                final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
+                if (isSwapped) {
+                    // todo
+                    //saveChanges();
+                    saveImmediate();
+                }
+                return isSwapped;
+            }
+
+            @Override
+            public boolean isLongPressDragEnabled() {
+                return false;
+            }
+
+            @Override
+            public boolean isItemViewSwipeEnabled() {
+                return false;
+            }
+
+            @Override
+            public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
+                                 final int swipeDir) {
+            }
+        };
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Utils
+    ///////////////////////////////////////////////////////////////////////////
+
+    private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
+        showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
+    }
+
+    private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
+        final DialogEditTextBinding dialogBinding
+                = DialogEditTextBinding.inflate(getLayoutInflater());
+        dialogBinding.dialogEditText.setHint(R.string.name);
+        dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
+        dialogBinding.dialogEditText.setText(selectedItem.name);
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setView(dialogBinding.getRoot())
+                .setPositiveButton(R.string.rename_playlist, (dialog, which) ->
+                        changeLocalPlaylistName(
+                                selectedItem.uid,
+                                dialogBinding.dialogEditText.getText().toString()))
+                .setNegativeButton(R.string.cancel, null)
+                .setNeutralButton(R.string.delete, (dialog, which) -> {
+                    showDeleteDialog(selectedItem.name,
+                            localPlaylistManager.deletePlaylist(selectedItem.uid));
+                    dialog.dismiss();
+                })
+                .create()
+                .show();
+    }
+
+    private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
+        if (activity == null || disposables == null) {
+            return;
+        }
+
+        new AlertDialog.Builder(activity)
+                .setTitle(name)
+                .setMessage(R.string.delete_playlist_prompt)
+                .setCancelable(true)
+                .setPositiveButton(R.string.delete, (dialog, i) ->
+                        disposables.add(deleteReactor
+                                .observeOn(AndroidSchedulers.mainThread())
+                                .subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
+                                        showError(new ErrorInfo(throwable,
+                                                UserAction.REQUESTED_BOOKMARK,
+                                                "Deleting playlist")))))
+                .setNegativeButton(R.string.cancel, null)
+                .show();
+    }
 }
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
index f8c5176ec..57a944709 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
@@ -1,8 +1,10 @@
 package org.schabi.newpipe.local.holder;
 
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
+import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
 import org.schabi.newpipe.local.LocalItemBuilder;
@@ -13,13 +15,16 @@ import org.schabi.newpipe.util.Localization;
 import java.time.format.DateTimeFormatter;
 
 public class LocalPlaylistItemHolder extends PlaylistItemHolder {
+    private final View itemHandleView;
+
     public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
-        super(infoItemBuilder, parent);
+        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
     }
 
     LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
                             final ViewGroup parent) {
         super(infoItemBuilder, layoutId, parent);
+        itemHandleView = itemView.findViewById(R.id.itemHandle);
     }
 
     @Override
@@ -38,6 +43,20 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
 
         PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
 
+        itemHandleView.setOnTouchListener(getOnTouchListener(item));
+
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
     }
+
+    private View.OnTouchListener getOnTouchListener(final PlaylistMetadataEntry item) {
+        return (view, motionEvent) -> {
+            view.performClick();
+            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
+                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                itemBuilder.getOnItemSelectedListener().drag(item,
+                        LocalPlaylistItemHolder.this);
+            }
+            return false;
+        };
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
index 440353ac7..9ecfa6979 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
@@ -1,8 +1,11 @@
 package org.schabi.newpipe.local.holder;
 
 import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 
+import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
 import org.schabi.newpipe.extractor.NewPipe;
@@ -14,14 +17,17 @@ import org.schabi.newpipe.util.Localization;
 import java.time.format.DateTimeFormatter;
 
 public class RemotePlaylistItemHolder extends PlaylistItemHolder {
+    private final View itemHandleView;
+
     public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
                                     final ViewGroup parent) {
-        super(infoItemBuilder, parent);
+        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
     }
 
     RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
                              final ViewGroup parent) {
         super(infoItemBuilder, layoutId, parent);
+        itemHandleView = itemView.findViewById(R.id.itemHandle);
     }
 
     @Override
@@ -46,6 +52,20 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
 
         PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
 
+        itemHandleView.setOnTouchListener(getOnTouchListener(item));
+
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
     }
+
+    private View.OnTouchListener getOnTouchListener(final PlaylistRemoteEntity item) {
+        return (view, motionEvent) -> {
+            view.performClick();
+            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
+                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                itemBuilder.getOnItemSelectedListener().drag(item,
+                        RemotePlaylistItemHolder.this);
+            }
+            return false;
+        };
+    }
 }
diff --git a/app/src/main/res/layout/list_playlist_bookmark_item.xml b/app/src/main/res/layout/list_playlist_bookmark_item.xml
new file mode 100644
index 000000000..642ea2949
--- /dev/null
+++ b/app/src/main/res/layout/list_playlist_bookmark_item.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/itemRoot"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/selectableItemBackground"
+    android:clickable="true"
+    android:focusable="true"
+    android:padding="@dimen/video_item_search_padding">
+
+    <ImageView
+        android:id="@+id/itemThumbnailView"
+        android:layout_width="90dp"
+        android:layout_height="50dp"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginRight="@dimen/video_item_search_image_right_margin"
+        android:scaleType="centerCrop"
+        android:src="@drawable/dummy_thumbnail_playlist"
+        tools:ignore="RtlHardcoded" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/itemStreamCountView"
+        android:layout_width="45dp"
+        android:layout_height="match_parent"
+        android:layout_alignTop="@id/itemThumbnailView"
+        android:layout_alignRight="@id/itemThumbnailView"
+        android:layout_alignBottom="@id/itemThumbnailView"
+        android:background="@color/playlist_stream_count_background_color"
+        android:gravity="center"
+        android:paddingTop="4dp"
+        android:paddingBottom="6dp"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="@color/duration_text_color"
+        android:textSize="@dimen/video_item_search_duration_text_size"
+        android:textStyle="bold"
+        app:drawableTint="@color/duration_text_color"
+        app:drawableTopCompat="@drawable/ic_playlist_play"
+        tools:ignore="RtlHardcoded"
+        tools:text="3141" />
+
+    <ImageView
+        android:id="@+id/itemHandle"
+        android:layout_width="wrap_content"
+        android:layout_height="55dp"
+        android:layout_alignParentRight="true"
+        android:layout_gravity="center_vertical"
+        android:contentDescription="@string/detail_drag_description"
+        android:paddingLeft="@dimen/video_item_search_image_right_margin"
+        android:scaleType="center"
+        app:srcCompat="@drawable/ic_drag_handle"
+        tools:ignore="RtlHardcoded,RtlSymmetry" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/itemTitleView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toStartOf="@id/itemHandle"
+        android:layout_toLeftOf="@id/itemHandle"
+        android:layout_toRightOf="@+id/itemThumbnailView"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textSize="@dimen/video_item_search_title_text_size"
+        tools:ignore="RtlHardcoded"
+        tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/itemUploaderView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/itemTitleView"
+        android:layout_toRightOf="@+id/itemThumbnailView"
+        android:lines="1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textSize="@dimen/video_item_search_uploader_text_size"
+        tools:ignore="RtlHardcoded"
+        tools:text="Uploader" />
+
+</RelativeLayout>

From 3c4882569931f91de0fc5dd0a980739781d6526c Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Fri, 15 Apr 2022 20:44:54 +0800
Subject: [PATCH 006/141] Debounced saver & bugfix & clean code

---
 .../database/playlist/PlaylistLocalItem.java  |   4 +
 .../playlist/PlaylistMetadataEntry.java       |   2 +-
 .../database/playlist/dao/PlaylistDAO.java    |  14 +
 .../playlist/dao/PlaylistStreamDAO.java       |  14 +-
 .../playlist/model/PlaylistEntity.java        |  11 +
 .../playlist/model/PlaylistRemoteEntity.java  |   2 +-
 .../newpipe/local/LocalItemListAdapter.java   |   1 -
 .../local/bookmark/BookmarkFragment.java      | 286 +++++++++++++-----
 .../local/dialog/PlaylistAppendDialog.java    |   2 +-
 .../local/playlist/LocalPlaylistManager.java  |  24 +-
 .../local/playlist/RemotePlaylistManager.java |  26 +-
 11 files changed, 288 insertions(+), 98 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 5bf50cd97..47c6dd617 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -45,6 +45,10 @@ public interface PlaylistLocalItem extends LocalItem {
             addItem(result, localPlaylists.get(i), itemsWithSameIndex);
             i++;
         }
+        while (j < remotePlaylists.size()) {
+            addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
+            j++;
+        }
         addItemsWithSameIndex(result, itemsWithSameIndex);
 
         return result;
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
index f54ffff13..ff80049a3 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
@@ -17,7 +17,7 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
     @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
     public final String thumbnailUrl;
     @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
-    public final long displayIndex;
+    public long displayIndex;
     @ColumnInfo(name = PLAYLIST_STREAM_COUNT)
     public final long streamCount;
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java
index 70aaa3b2d..d8071e0af 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.dao;
 
 import androidx.room.Dao;
 import androidx.room.Query;
+import androidx.room.Transaction;
 
 import org.schabi.newpipe.database.BasicDAO;
 import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
@@ -36,4 +37,17 @@ public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
 
     @Query("SELECT COUNT(*) FROM " + PLAYLIST_TABLE)
     Flowable<Long> getCount();
+
+    @Transaction
+    default long upsertPlaylist(final PlaylistEntity playlist) {
+        final long playlistId = playlist.getUid();
+
+        if (playlistId == -1) {
+            // This situation is probably impossible.
+            return insert(playlist);
+        } else {
+            update(playlist);
+            return playlistId;
+        }
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
index 3fb96a21f..0fce984f3 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
@@ -82,7 +82,19 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
             + " FROM " + PLAYLIST_TABLE
             + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
             + " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
-            + " GROUP BY " + JOIN_PLAYLIST_ID
+            + " GROUP BY " + PLAYLIST_ID
             + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
     Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
+
+    @Transaction
+    @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
+            + PLAYLIST_DISPLAY_INDEX + ", "
+            + "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
+
+            + " FROM " + PLAYLIST_TABLE
+            + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+            + " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
+            + " GROUP BY " + PLAYLIST_ID
+            + " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
+    Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylistMetadata();
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index cdbbdebc0..272e8a5bc 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -2,12 +2,15 @@ package org.schabi.newpipe.database.playlist.model;
 
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
+import androidx.room.Ignore;
 import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
 
+import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
+
 @Entity(tableName = PLAYLIST_TABLE,
         indices = {@Index(value = {PLAYLIST_NAME})})
 public class PlaylistEntity {
@@ -36,6 +39,14 @@ public class PlaylistEntity {
         this.displayIndex = displayIndex;
     }
 
+    @Ignore
+    public PlaylistEntity(final PlaylistMetadataEntry item) {
+        this.uid = item.uid;
+        this.name = item.name;
+        this.thumbnailUrl = item.thumbnailUrl;
+        this.displayIndex = item.displayIndex;
+    }
+
     public long getUid() {
         return uid;
     }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
index 454526769..adea2738b 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
@@ -54,7 +54,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
     private String uploader;
 
     @ColumnInfo(name = REMOTE_PLAYLIST_DISPLAY_INDEX)
-    private long displayIndex;
+    private long displayIndex = -1; // Make sure the new item is on the top
 
     @ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
     private Long streamCount;
diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index e2bfd5977..05e2fdac0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -142,7 +142,6 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
     }
 
     public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
-        // todo: reuse this code?
         final int actualFrom = adapterOffsetWithoutHeader(fromAdapterPosition);
         final int actualTo = adapterOffsetWithoutHeader(toAdapterPosition);
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 63c63c1e0..4eac0f5ea 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -6,6 +6,7 @@ import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.InputType;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -30,21 +31,32 @@ import org.schabi.newpipe.databinding.DialogEditTextBinding;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.BaseLocalListFragment;
+import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
+import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
 import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
 import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import icepick.State;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.subjects.PublishSubject;
 
 public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
+    // todo: add to playlists, item handle should be invisible
+
+    // Save the list 10s after the last change occurred
+    private static final long SAVE_DEBOUNCE_MILLIS = 10000;
     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
     protected Parcelable itemsListState;
@@ -55,6 +67,16 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     private RemotePlaylistManager remotePlaylistManager;
     private ItemTouchHelper itemTouchHelper;
 
+    private PublishSubject<Long> debouncedSaveSignal;
+
+    /* Has the playlist been fully loaded from db */
+    private AtomicBoolean isLoadingComplete;
+    /* Has the playlist been modified (e.g. items reordered or deleted) */
+    private AtomicBoolean isModified;
+
+    // Map from (uid, local/remote item) to the saved display index in the database.
+    private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
+
     ///////////////////////////////////////////////////////////////////////////
     // Fragment LifeCycle - Creation
     ///////////////////////////////////////////////////////////////////////////
@@ -69,6 +91,12 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         localPlaylistManager = new LocalPlaylistManager(database);
         remotePlaylistManager = new RemotePlaylistManager(database);
         disposables = new CompositeDisposable();
+
+        debouncedSaveSignal = PublishSubject.create();
+        isLoadingComplete = new AtomicBoolean();
+        isModified = new AtomicBoolean();
+
+        displayIndexInDatabase = new HashMap<>();
     }
 
     @Nullable
@@ -154,6 +182,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     public void startLoading(final boolean forceLoad) {
         super.startLoading(forceLoad);
 
+        disposables.add(getDebouncedSaver());
+        isLoadingComplete.set(false);
+        isModified.set(false);
+
         Flowable.combineLatest(localPlaylistManager.getPlaylists(),
                 remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
                 .onBackpressureLatest()
@@ -169,6 +201,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     public void onPause() {
         super.onPause();
         itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
+
+        // Save on exit
+        saveImmediate();
     }
 
     @Override
@@ -189,14 +224,22 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     @Override
     public void onDestroy() {
         super.onDestroy();
+        if (debouncedSaveSignal != null) {
+            debouncedSaveSignal.onComplete();
+        }
         if (disposables != null) {
             disposables.dispose();
         }
 
+        debouncedSaveSignal = null;
         disposables = null;
         localPlaylistManager = null;
         remotePlaylistManager = null;
         itemsListState = null;
+
+        isLoadingComplete = null;
+        isModified = null;
+        displayIndexInDatabase = null;
     }
 
     ///////////////////////////////////////////////////////////////////////////
@@ -208,6 +251,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             @Override
             public void onSubscribe(final Subscription s) {
                 showLoading();
+                isLoadingComplete.set(false);
+
                 if (databaseSubscription != null) {
                     databaseSubscription.cancel();
                 }
@@ -217,14 +262,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
             @Override
             public void onNext(final List<PlaylistLocalItem> subscriptions) {
-
-                // If displayIndex does not match actual index, update displayIndex.
-                // This may happen when a new list is created
-                // or on the first run after database update
-                // or displayIndex is not continuous for some reason.
-                checkDisplayIndexUpdate(subscriptions);
-
-                handleResult(subscriptions);
+                if (isModified == null || !isModified.get()) {
+                    checkDisplayIndexModified(subscriptions);
+                    handleResult(subscriptions);
+                    isLoadingComplete.set(true);
+                }
                 if (databaseSubscription != null) {
                     databaseSubscription.request(1);
                 }
@@ -296,86 +338,170 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         disposables.add(disposable);
     }
 
-    private void changeLocalPlaylistDisplayIndex(final long id, final long displayIndex) {
+    private void deleteItem(final PlaylistLocalItem item) {
+        if (itemListAdapter == null) {
+            return;
+        }
+        itemListAdapter.removeItem(item);
 
-        if (localPlaylistManager == null) {
+        saveChanges();
+    }
+
+    private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
+        if (isModified != null && isModified.get()) {
             return;
         }
 
-        if (DEBUG) {
-            Log.d(TAG, "Updating local playlist id=[" + id + "] "
-                    + "with new display_index=[" + displayIndex + "]");
-        }
+        displayIndexInDatabase.clear();
 
-        final Disposable disposable =
-                localPlaylistManager.changePlaylistDisplayIndex(id, displayIndex)
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
-                                new ErrorInfo(throwable,
-                                        UserAction.REQUESTED_BOOKMARK,
-                                        "Changing local playlist display_index")));
-        disposables.add(disposable);
-    }
-
-    private void changeRemotePlaylistDisplayIndex(final long id, final long displayIndex) {
-
-        if (remotePlaylistManager == null) {
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Updating remote playlist id=[" + id + "] "
-                    + "with new display_index=[" + displayIndex + "]");
-        }
-
-        final Disposable disposable =
-                remotePlaylistManager.changePlaylistDisplayIndex(id, displayIndex)
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
-                                new ErrorInfo(throwable,
-                                        UserAction.REQUESTED_BOOKMARK,
-                                        "Changing remote playlist display_index")));
-        disposables.add(disposable);
-    }
-
-    private void checkDisplayIndexUpdate(@NonNull final List<PlaylistLocalItem> result) {
+        // If the display index does not match actual index in the list, update the display index.
+        // This may happen when a new list is created
+        // or on the first run after database update
+        // or displayIndex is not continuous for some reason.
+        boolean isDisplayIndexModified = false;
         for (int i = 0; i < result.size(); i++) {
             final PlaylistLocalItem item = result.get(i);
             if (item.getDisplayIndex() != i) {
-                if (item instanceof PlaylistMetadataEntry) {
-                    changeLocalPlaylistDisplayIndex(((PlaylistMetadataEntry) item).uid, i);
-                } else if (item instanceof PlaylistRemoteEntity) {
-                    changeRemotePlaylistDisplayIndex(((PlaylistRemoteEntity) item).getUid(), i);
-                }
+                isDisplayIndexModified = true;
             }
+
+            // Updating display index in the item does not affect the value inserts into
+            // database, which will be recalculated during the database update. Updating
+            // display index in the item here is to determine whether it is recently modified.
+            // Save the index read from the database.
+            if (item instanceof PlaylistMetadataEntry) {
+
+                displayIndexInDatabase.put(new Pair<>(((PlaylistMetadataEntry) item).uid,
+                        LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM), item.getDisplayIndex());
+                ((PlaylistMetadataEntry) item).displayIndex = i;
+
+            } else if (item instanceof PlaylistRemoteEntity) {
+
+                displayIndexInDatabase.put(new Pair<>(((PlaylistRemoteEntity) item).getUid(),
+                                LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM),
+                        item.getDisplayIndex());
+                ((PlaylistRemoteEntity) item).setDisplayIndex(i);
+
+            }
+        }
+
+        if (isDisplayIndexModified) {
+            saveChanges();
         }
     }
 
-    private void saveImmediate() {
-        if (localPlaylistManager == null || remotePlaylistManager == null
-                || itemListAdapter == null) {
+    private void saveChanges() {
+        if (isModified == null || debouncedSaveSignal == null) {
             return;
         }
-        // todo: debounce
-        /*
+
+        isModified.set(true);
+        debouncedSaveSignal.onNext(System.currentTimeMillis());
+    }
+
+    private Disposable getDebouncedSaver() {
+        if (debouncedSaveSignal == null) {
+            return Disposable.empty();
+        }
+
+        return debouncedSaveSignal
+                .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(ignored -> saveImmediate(), throwable ->
+                        showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
+                                "Debounced saver")));
+    }
+
+    private void saveImmediate() {
+        if (itemListAdapter == null) {
+            return;
+        }
+
         // List must be loaded and modified in order to save
         if (isLoadingComplete == null || isModified == null
                 || !isLoadingComplete.get() || !isModified.get()) {
-            Log.w(TAG, "Attempting to save playlist when local playlist "
-                    + "is not loaded or not modified: playlist id=[" + playlistId + "]");
+            Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
+                    + "is not loaded or playlists not modified");
             return;
         }
-        */
-        // todo: is it correct?
+
         final List<LocalItem> items = itemListAdapter.getItemsList();
+        final List<PlaylistMetadataEntry> localItemsUpdate = new ArrayList<>();
+        final List<Long> localItemsDeleteUid = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remoteItemsUpdate = new ArrayList<>();
+        final List<Long> remoteItemsDeleteUid = new ArrayList<>();
+
+        // Calculate display index
         for (int i = 0; i < items.size(); i++) {
             final LocalItem item = items.get(i);
+
             if (item instanceof PlaylistMetadataEntry) {
-                changeLocalPlaylistDisplayIndex(((PlaylistMetadataEntry) item).uid, i);
+                ((PlaylistMetadataEntry) item).displayIndex = i;
+
+                final Long uid = ((PlaylistMetadataEntry) item).uid;
+                final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
+                        LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM);
+                final Long databaseIndex = displayIndexInDatabase.remove(key);
+
+                if (databaseIndex != null) {
+                    if (databaseIndex != i) {
+                        localItemsUpdate.add((PlaylistMetadataEntry) item);
+                    }
+                } else {
+                    // This should be impossible.
+                    continue;
+                }
             } else if (item instanceof PlaylistRemoteEntity) {
-                changeLocalPlaylistDisplayIndex(((PlaylistRemoteEntity) item).getUid(), i);
+                ((PlaylistRemoteEntity) item).setDisplayIndex(i);
+
+                final Long uid = ((PlaylistRemoteEntity) item).getUid();
+                final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
+                        LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM);
+                final Long databaseIndex = displayIndexInDatabase.remove(key);
+
+                if (databaseIndex != null) {
+                    if (databaseIndex != i) {
+                        remoteItemsUpdate.add((PlaylistRemoteEntity) item);
+                    }
+                } else {
+                    // This should be impossible.
+                    continue;
+                }
             }
         }
+
+        // Find deleted items
+        for (final Pair<Long, LocalItem.LocalItemType> key : displayIndexInDatabase.keySet()) {
+            if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
+                localItemsDeleteUid.add(key.first);
+            } else if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
+                remoteItemsDeleteUid.add(key.first);
+            }
+        }
+
+        displayIndexInDatabase.clear();
+
+        // 1. Update local playlists
+        // 2. Update remote playlists
+        // 3. Set isModified false
+        disposables.add(localPlaylistManager.updatePlaylists(localItemsUpdate, localItemsDeleteUid)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(() -> disposables.add(remotePlaylistManager.updatePlaylists(
+                        remoteItemsUpdate, remoteItemsDeleteUid)
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe(() -> {
+                                    if (isModified != null) {
+                                        isModified.set(false);
+                                    }
+                                },
+                                throwable -> showError(new ErrorInfo(throwable,
+                                        UserAction.REQUESTED_BOOKMARK,
+                                        "Saving playlist"))
+                        )),
+                        throwable -> showError(new ErrorInfo(throwable,
+                                UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
+                ));
+
     }
 
     private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
@@ -404,17 +530,26 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                                   @NonNull final RecyclerView.ViewHolder target) {
                 if (source.getItemViewType() != target.getItemViewType()
                         || itemListAdapter == null) {
-                    return false;
+                    // Allow swap LocalPlaylistItemHolder and RemotePlaylistItemHolder.
+                    if (!(
+                            (
+                                    (source instanceof LocalPlaylistItemHolder)
+                                            || (source instanceof RemotePlaylistItemHolder)
+                            )
+                                    && (
+                                    (target instanceof LocalPlaylistItemHolder)
+                                            || (target instanceof RemotePlaylistItemHolder)
+                            )
+                    )) {
+                        return false;
+                    }
                 }
 
-                // todo: is it correct
                 final int sourceIndex = source.getBindingAdapterPosition();
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
                 if (isSwapped) {
-                    // todo
-                    //saveChanges();
-                    saveImmediate();
+                    saveChanges();
                 }
                 return isSwapped;
             }
@@ -441,7 +576,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     ///////////////////////////////////////////////////////////////////////////
 
     private void showRemoteDeleteDialog(final PlaylistRemoteEntity item) {
-        showDeleteDialog(item.getName(), remotePlaylistManager.deletePlaylist(item.getUid()));
+        showDeleteDialog(item.getName(), item);
     }
 
     private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
@@ -459,15 +594,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                                 dialogBinding.dialogEditText.getText().toString()))
                 .setNegativeButton(R.string.cancel, null)
                 .setNeutralButton(R.string.delete, (dialog, which) -> {
-                    showDeleteDialog(selectedItem.name,
-                            localPlaylistManager.deletePlaylist(selectedItem.uid));
+                    showDeleteDialog(selectedItem.name, selectedItem);
                     dialog.dismiss();
                 })
                 .create()
                 .show();
     }
 
-    private void showDeleteDialog(final String name, final Single<Integer> deleteReactor) {
+    private void showDeleteDialog(final String name, final PlaylistLocalItem item) {
         if (activity == null || disposables == null) {
             return;
         }
@@ -476,13 +610,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 .setTitle(name)
                 .setMessage(R.string.delete_playlist_prompt)
                 .setCancelable(true)
-                .setPositiveButton(R.string.delete, (dialog, i) ->
-                        disposables.add(deleteReactor
-                                .observeOn(AndroidSchedulers.mainThread())
-                                .subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
-                                        showError(new ErrorInfo(throwable,
-                                                UserAction.REQUESTED_BOOKMARK,
-                                                "Deleting playlist")))))
+                .setPositiveButton(R.string.delete, (dialog, i) -> deleteItem(item))
                 .setNegativeButton(R.string.cancel, null)
                 .show();
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
index a874cdd62..58a10af22 100644
--- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
@@ -85,7 +85,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
         final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
         newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());
 
-        playlistDisposables.add(playlistManager.getPlaylists()
+        playlistDisposables.add(playlistManager.getDisplayIndexOrderedPlaylists()
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(this::onPlaylistsReceived));
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
index 47817f9e4..c68a22b01 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
@@ -41,6 +41,7 @@ public class LocalPlaylistManager {
         }
         final StreamEntity defaultStream = streams.get(0);
 
+        // Save to the database directly.
         // Make sure the new playlist is always on the top of bookmark.
         // The index will be reassigned to non-negative number in BookmarkFragment.
         final PlaylistEntity newPlaylist =
@@ -85,10 +86,31 @@ public class LocalPlaylistManager {
         })).subscribeOn(Schedulers.io());
     }
 
+    public Completable updatePlaylists(final List<PlaylistMetadataEntry> updateItems,
+                                       final List<Long> deletedItems) {
+        final List<PlaylistEntity> items = new ArrayList<>(updateItems.size());
+        for (final PlaylistMetadataEntry item : updateItems) {
+            items.add(new PlaylistEntity(item));
+        }
+        return Completable.fromRunnable(() -> database.runInTransaction(() -> {
+            for (final Long uid: deletedItems) {
+                playlistTable.deletePlaylist(uid);
+            }
+            for (final PlaylistEntity item: items) {
+                playlistTable.upsertPlaylist(item);
+            }
+        })).subscribeOn(Schedulers.io());
+    }
+
     public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
         return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
     }
 
+    public Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylists() {
+        return playlistStreamTable.getDisplayIndexOrderedPlaylistMetadata()
+                .subscribeOn(Schedulers.io());
+    }
+
     public Flowable<List<PlaylistStreamEntry>> getPlaylistStreams(final long playlistId) {
         return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io());
     }
@@ -107,7 +129,7 @@ public class LocalPlaylistManager {
         return modifyPlaylist(playlistId, null, thumbnailUrl, -1);
     }
 
-    public Maybe<Integer> changePlaylistDisplayIndex(final long playlistId,
+    public Maybe<Integer> updatePlaylistDisplayIndex(final long playlistId,
                                                      final long displayIndex) {
         return modifyPlaylist(playlistId, null, null, displayIndex);
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
index b49f149d6..1dbd726ae 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
@@ -7,16 +7,18 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
 
 import java.util.List;
 
+import io.reactivex.rxjava3.core.Completable;
 import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Maybe;
 import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class RemotePlaylistManager {
 
+    private final AppDatabase database;
     private final PlaylistRemoteDAO playlistRemoteTable;
 
     public RemotePlaylistManager(final AppDatabase db) {
+        database = db;
         playlistRemoteTable = db.playlistRemoteDAO();
     }
 
@@ -34,18 +36,16 @@ public class RemotePlaylistManager {
                 .subscribeOn(Schedulers.io());
     }
 
-    public Maybe<Integer> changePlaylistDisplayIndex(final long playlistId,
-                                                     final long displayIndex) {
-        return playlistRemoteTable.getPlaylist(playlistId)
-                .firstElement()
-                .filter(playlistRemoteEntities -> !playlistRemoteEntities.isEmpty())
-                .map(playlistRemoteEntities -> {
-                    final PlaylistRemoteEntity playlist = playlistRemoteEntities.get(0);
-                    if (displayIndex != -1) {
-                        playlist.setDisplayIndex(displayIndex);
-                    }
-                    return playlistRemoteTable.update(playlist);
-                }).subscribeOn(Schedulers.io());
+    public Completable updatePlaylists(final List<PlaylistRemoteEntity> updateItems,
+                                       final List<Long> deletedItems) {
+        return Completable.fromRunnable(() -> database.runInTransaction(() -> {
+            for (final Long uid: deletedItems) {
+                playlistRemoteTable.deletePlaylist(uid);
+            }
+            for (final PlaylistRemoteEntity item: updateItems) {
+                playlistRemoteTable.upsert(item);
+            }
+        })).subscribeOn(Schedulers.io());
     }
 
     public Single<Long> onBookmark(final PlaylistInfo playlistInfo) {

From 0aa08a5e4049952bc830522b3053d347199ee3f7 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Fri, 15 Apr 2022 23:19:24 +0800
Subject: [PATCH 007/141] Use new item holder

---
 .../newpipe/local/LocalItemListAdapter.java   | 21 ++++--
 .../local/bookmark/BookmarkFragment.java      | 18 ++---
 .../LocalBookmarkPlaylistItemHolder.java      | 63 ++++++++++++++++
 .../local/holder/LocalPlaylistItemHolder.java | 19 +----
 .../RemoteBookmarkPlaylistItemHolder.java     | 71 +++++++++++++++++++
 .../holder/RemotePlaylistItemHolder.java      | 20 +-----
 6 files changed, 163 insertions(+), 49 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
 create mode 100644 app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java

diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index 05e2fdac0..161d35ee5 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.stream.model.StreamStateEntity;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.holder.LocalItemHolder;
 import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
 import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
@@ -20,6 +21,7 @@ import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
 import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
 import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
 import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
+import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
 import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
 import org.schabi.newpipe.util.FallbackViewHolder;
@@ -66,6 +68,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
     private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
     private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
     private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
+    private static final int LOCAL_BOOKMARK_PLAYLIST_HOLDER_TYPE = 0x2008;
+    private static final int REMOTE_BOOKMARK_PLAYLIST_HOLDER_TYPE = 0x2010;
 
     private final LocalItemBuilder localItemBuilder;
     private final ArrayList<LocalItem> localItems;
@@ -74,6 +78,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
 
     private boolean showFooter = false;
     private boolean useGridVariant = false;
+    private boolean useItemHandle = false;
     private View header = null;
     private View footer = null;
 
@@ -169,6 +174,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
         this.useGridVariant = useGridVariant;
     }
 
+    public void setUseItemHandle(final boolean useItemHandle) {
+        this.useItemHandle = useItemHandle;
+    }
+
     public void setHeader(final View header) {
         final boolean changed = header != this.header;
         this.header = header;
@@ -247,11 +256,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
 
         switch (item.getLocalItemType()) {
             case PLAYLIST_LOCAL_ITEM:
-                return useGridVariant
-                        ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
+                return useItemHandle ? LOCAL_BOOKMARK_PLAYLIST_HOLDER_TYPE : (useGridVariant
+                        ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE);
             case PLAYLIST_REMOTE_ITEM:
-                return useGridVariant
-                        ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
+                return useItemHandle ? REMOTE_BOOKMARK_PLAYLIST_HOLDER_TYPE : (useGridVariant
+                        ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE);
 
             case PLAYLIST_STREAM_ITEM:
                 return useGridVariant
@@ -283,10 +292,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
                 return new LocalPlaylistItemHolder(localItemBuilder, parent);
             case LOCAL_PLAYLIST_GRID_HOLDER_TYPE:
                 return new LocalPlaylistGridItemHolder(localItemBuilder, parent);
+            case LOCAL_BOOKMARK_PLAYLIST_HOLDER_TYPE:
+                return new LocalBookmarkPlaylistItemHolder(localItemBuilder, parent);
             case REMOTE_PLAYLIST_HOLDER_TYPE:
                 return new RemotePlaylistItemHolder(localItemBuilder, parent);
             case REMOTE_PLAYLIST_GRID_HOLDER_TYPE:
                 return new RemotePlaylistGridItemHolder(localItemBuilder, parent);
+            case REMOTE_BOOKMARK_PLAYLIST_HOLDER_TYPE:
+                return new RemoteBookmarkPlaylistItemHolder(localItemBuilder, parent);
             case STREAM_PLAYLIST_HOLDER_TYPE:
                 return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
             case STREAM_PLAYLIST_GRID_HOLDER_TYPE:
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 4eac0f5ea..daa89a4f0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -31,8 +31,8 @@ import org.schabi.newpipe.databinding.DialogEditTextBinding;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.BaseLocalListFragment;
-import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
-import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
+import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
+import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
 import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
 import org.schabi.newpipe.util.NavigationHelper;
@@ -53,7 +53,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
 import io.reactivex.rxjava3.subjects.PublishSubject;
 
 public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
-    // todo: add to playlists, item handle should be invisible
 
     // Save the list 10s after the last change occurred
     private static final long SAVE_DEBOUNCE_MILLIS = 10000;
@@ -126,6 +125,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     @Override
     protected void initViews(final View rootView, final Bundle savedInstanceState) {
         super.initViews(rootView, savedInstanceState);
+
+        itemListAdapter.setUseItemHandle(true);
     }
 
     @Override
@@ -530,15 +531,16 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                                   @NonNull final RecyclerView.ViewHolder target) {
                 if (source.getItemViewType() != target.getItemViewType()
                         || itemListAdapter == null) {
-                    // Allow swap LocalPlaylistItemHolder and RemotePlaylistItemHolder.
+                    // Allow swap LocalBookmarkPlaylistItemHolder and
+                    // RemoteBookmarkPlaylistItemHolder.
                     if (!(
                             (
-                                    (source instanceof LocalPlaylistItemHolder)
-                                            || (source instanceof RemotePlaylistItemHolder)
+                                    (source instanceof LocalBookmarkPlaylistItemHolder)
+                                            || (source instanceof RemoteBookmarkPlaylistItemHolder)
                             )
                                     && (
-                                    (target instanceof LocalPlaylistItemHolder)
-                                            || (target instanceof RemotePlaylistItemHolder)
+                                    (target instanceof LocalBookmarkPlaylistItemHolder)
+                                            || (target instanceof RemoteBookmarkPlaylistItemHolder)
                             )
                     )) {
                         return false;
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
new file mode 100644
index 000000000..697ef1072
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
@@ -0,0 +1,63 @@
+package org.schabi.newpipe.local.holder;
+
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.database.LocalItem;
+import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
+import org.schabi.newpipe.local.LocalItemBuilder;
+import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.PicassoHelper;
+
+import java.time.format.DateTimeFormatter;
+
+public class LocalBookmarkPlaylistItemHolder extends PlaylistItemHolder {
+    private final View itemHandleView;
+
+    public LocalBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
+                                           final ViewGroup parent) {
+        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
+    }
+
+    LocalBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
+                                    final ViewGroup parent) {
+        super(infoItemBuilder, layoutId, parent);
+        itemHandleView = itemView.findViewById(R.id.itemHandle);
+    }
+
+    @Override
+    public void updateFromItem(final LocalItem localItem,
+                               final HistoryRecordManager historyRecordManager,
+                               final DateTimeFormatter dateTimeFormatter) {
+        if (!(localItem instanceof PlaylistMetadataEntry)) {
+            return;
+        }
+        final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
+
+        itemTitleView.setText(item.name);
+        itemStreamCountView.setText(Localization.localizeStreamCountMini(
+                itemStreamCountView.getContext(), item.streamCount));
+        itemUploaderView.setVisibility(View.INVISIBLE);
+
+        PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
+
+        itemHandleView.setOnTouchListener(getOnTouchListener(item));
+
+        super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
+    }
+
+    private View.OnTouchListener getOnTouchListener(final PlaylistMetadataEntry item) {
+        return (view, motionEvent) -> {
+            view.performClick();
+            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
+                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                itemBuilder.getOnItemSelectedListener().drag(item,
+                        LocalBookmarkPlaylistItemHolder.this);
+            }
+            return false;
+        };
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
index 57a944709..2cfc94463 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
@@ -1,6 +1,5 @@
 package org.schabi.newpipe.local.holder;
 
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -15,16 +14,14 @@ import org.schabi.newpipe.util.Localization;
 import java.time.format.DateTimeFormatter;
 
 public class LocalPlaylistItemHolder extends PlaylistItemHolder {
-    private final View itemHandleView;
 
     public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
-        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
+        this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
     }
 
     LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
                             final ViewGroup parent) {
         super(infoItemBuilder, layoutId, parent);
-        itemHandleView = itemView.findViewById(R.id.itemHandle);
     }
 
     @Override
@@ -43,20 +40,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
 
         PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
 
-        itemHandleView.setOnTouchListener(getOnTouchListener(item));
-
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
     }
-
-    private View.OnTouchListener getOnTouchListener(final PlaylistMetadataEntry item) {
-        return (view, motionEvent) -> {
-            view.performClick();
-            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
-                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                itemBuilder.getOnItemSelectedListener().drag(item,
-                        LocalPlaylistItemHolder.this);
-            }
-            return false;
-        };
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java
new file mode 100644
index 000000000..345223b08
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java
@@ -0,0 +1,71 @@
+package org.schabi.newpipe.local.holder;
+
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.database.LocalItem;
+import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.local.LocalItemBuilder;
+import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.PicassoHelper;
+
+import java.time.format.DateTimeFormatter;
+
+public class RemoteBookmarkPlaylistItemHolder extends PlaylistItemHolder {
+    private final View itemHandleView;
+
+    public RemoteBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
+                                            final ViewGroup parent) {
+        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
+    }
+
+    RemoteBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
+                                     final ViewGroup parent) {
+        super(infoItemBuilder, layoutId, parent);
+        itemHandleView = itemView.findViewById(R.id.itemHandle);
+    }
+
+    @Override
+    public void updateFromItem(final LocalItem localItem,
+                               final HistoryRecordManager historyRecordManager,
+                               final DateTimeFormatter dateTimeFormatter) {
+        if (!(localItem instanceof PlaylistRemoteEntity)) {
+            return;
+        }
+        final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
+
+        itemTitleView.setText(item.getName());
+        itemStreamCountView.setText(Localization.localizeStreamCountMini(
+                itemStreamCountView.getContext(), item.getStreamCount()));
+        // Here is where the uploader name is set in the bookmarked playlists library
+        if (!TextUtils.isEmpty(item.getUploader())) {
+            itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(),
+                    NewPipe.getNameOfService(item.getServiceId())));
+        } else {
+            itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId()));
+        }
+
+        PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
+
+        itemHandleView.setOnTouchListener(getOnTouchListener(item));
+
+        super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
+    }
+
+    private View.OnTouchListener getOnTouchListener(final PlaylistRemoteEntity item) {
+        return (view, motionEvent) -> {
+            view.performClick();
+            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
+                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                itemBuilder.getOnItemSelectedListener().drag(item,
+                        RemoteBookmarkPlaylistItemHolder.this);
+            }
+            return false;
+        };
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
index 9ecfa6979..d2059bfed 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
@@ -1,8 +1,6 @@
 package org.schabi.newpipe.local.holder;
 
 import android.text.TextUtils;
-import android.view.MotionEvent;
-import android.view.View;
 import android.view.ViewGroup;
 
 import org.schabi.newpipe.R;
@@ -17,17 +15,15 @@ import org.schabi.newpipe.util.Localization;
 import java.time.format.DateTimeFormatter;
 
 public class RemotePlaylistItemHolder extends PlaylistItemHolder {
-    private final View itemHandleView;
 
     public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
                                     final ViewGroup parent) {
-        this(infoItemBuilder, R.layout.list_playlist_bookmark_item, parent);
+        this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
     }
 
     RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
                              final ViewGroup parent) {
         super(infoItemBuilder, layoutId, parent);
-        itemHandleView = itemView.findViewById(R.id.itemHandle);
     }
 
     @Override
@@ -52,20 +48,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
 
         PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
 
-        itemHandleView.setOnTouchListener(getOnTouchListener(item));
-
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
     }
-
-    private View.OnTouchListener getOnTouchListener(final PlaylistRemoteEntity item) {
-        return (view, motionEvent) -> {
-            view.performClick();
-            if (itemBuilder != null && itemBuilder.getOnItemSelectedListener() != null
-                    && motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                itemBuilder.getOnItemSelectedListener().drag(item,
-                        RemotePlaylistItemHolder.this);
-            }
-            return false;
-        };
-    }
 }

From c24aed054f9da7dcde634faf570f0314d44f6a87 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Sat, 16 Apr 2022 12:00:02 +0800
Subject: [PATCH 008/141] Fix sonar warning and typo

---
 .../schabi/newpipe/database/Migrations.java   | 16 ++---
 .../newpipe/local/LocalItemListAdapter.java   | 15 +++--
 .../local/bookmark/BookmarkFragment.java      | 64 +++++++++----------
 .../LocalBookmarkPlaylistItemHolder.java      | 11 +---
 .../local/holder/LocalPlaylistItemHolder.java |  3 +-
 .../RemoteBookmarkPlaylistItemHolder.java     | 19 +-----
 .../holder/RemotePlaylistItemHolder.java      |  3 +-
 7 files changed, 53 insertions(+), 78 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index ffca6cca5..1013899ca 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -195,8 +195,8 @@ public final class Migrations {
             try {
                 database.beginTransaction();
 
-                // update playlists
-                // create a temp table to initialize display_index
+                // Update playlists.
+                // Create a temp table to initialize display_index.
                 database.execSQL("CREATE TABLE `playlists_tmp` "
                         + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                         + "`name` TEXT, `thumbnail_url` TEXT,"
@@ -204,16 +204,16 @@ public final class Migrations {
                 database.execSQL("INSERT INTO `playlists_tmp` (`uid`, `name`, `thumbnail_url`)"
                         + "SELECT `uid`, `name`, `thumbnail_url` FROM `playlists`");
 
-                // replace the old table
+                // Replace the old table.
                 database.execSQL("DROP TABLE `playlists`");
                 database.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`");
 
-                // create index on the new table
+                // Create index on the new table.
                 database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
 
 
-                // update remote_playlists
-                // create a temp table to initialize display_index
+                // Update remote_playlists.
+                // Create a temp table to initialize display_index.
                 database.execSQL("CREATE TABLE `remote_playlists_tmp` "
                         + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                         + "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
@@ -225,11 +225,11 @@ public final class Migrations {
                         + "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
                         + "`stream_count` FROM `remote_playlists`");
 
-                // replace the old table
+                // Replace the old table.
                 database.execSQL("DROP TABLE `remote_playlists`");
                 database.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`");
 
-                // create index on the new table
+                // Create index on the new table.
                 database.execSQL("CREATE INDEX `index_remote_playlists_name` "
                         + "ON `remote_playlists` (`name`)");
                 database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index 161d35ee5..5c22cee24 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -256,12 +256,17 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
 
         switch (item.getLocalItemType()) {
             case PLAYLIST_LOCAL_ITEM:
-                return useItemHandle ? LOCAL_BOOKMARK_PLAYLIST_HOLDER_TYPE : (useGridVariant
-                        ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE);
+                if (useItemHandle) {
+                    return LOCAL_BOOKMARK_PLAYLIST_HOLDER_TYPE;
+                }
+                return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE
+                        : LOCAL_PLAYLIST_HOLDER_TYPE;
             case PLAYLIST_REMOTE_ITEM:
-                return useItemHandle ? REMOTE_BOOKMARK_PLAYLIST_HOLDER_TYPE : (useGridVariant
-                        ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE);
-
+                if (useItemHandle) {
+                    return REMOTE_BOOKMARK_PLAYLIST_HOLDER_TYPE;
+                }
+                return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE
+                        : REMOTE_PLAYLIST_HOLDER_TYPE;
             case PLAYLIST_STREAM_ITEM:
                 return useGridVariant
                         ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index daa89a4f0..14100ef81 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -54,7 +54,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
 
 public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
 
-    // Save the list 10s after the last change occurred
+    // Save the list 10 seconds after the last change occurred
     private static final long SAVE_DEBOUNCE_MILLIS = 10000;
     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
@@ -281,6 +281,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
             @Override
             public void onComplete() {
+                // Do nothing.
             }
         };
     }
@@ -444,13 +445,13 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                         LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM);
                 final Long databaseIndex = displayIndexInDatabase.remove(key);
 
+                // The database index should not be null because inserting new item into database
+                // is not dealt here. NullPointerException has occurred once, but I can't reproduce
+                // it. Enhance robustness here.
                 if (databaseIndex != null) {
                     if (databaseIndex != i) {
                         localItemsUpdate.add((PlaylistMetadataEntry) item);
                     }
-                } else {
-                    // This should be impossible.
-                    continue;
                 }
             } else if (item instanceof PlaylistRemoteEntity) {
                 ((PlaylistRemoteEntity) item).setDisplayIndex(i);
@@ -464,9 +465,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                     if (databaseIndex != i) {
                         remoteItemsUpdate.add((PlaylistRemoteEntity) item);
                     }
-                } else {
-                    // This should be impossible.
-                    continue;
                 }
             }
         }
@@ -489,16 +487,16 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(() -> disposables.add(remotePlaylistManager.updatePlaylists(
                         remoteItemsUpdate, remoteItemsDeleteUid)
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(() -> {
-                                    if (isModified != null) {
-                                        isModified.set(false);
-                                    }
-                                },
-                                throwable -> showError(new ErrorInfo(throwable,
-                                        UserAction.REQUESTED_BOOKMARK,
-                                        "Saving playlist"))
-                        )),
+                                .observeOn(AndroidSchedulers.mainThread())
+                                .subscribe(() -> {
+                                            if (isModified != null) {
+                                                isModified.set(false);
+                                            }
+                                        },
+                                        throwable -> showError(new ErrorInfo(throwable,
+                                                UserAction.REQUESTED_BOOKMARK,
+                                                "Saving playlist"))
+                                )),
                         throwable -> showError(new ErrorInfo(throwable,
                                 UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
                 ));
@@ -529,22 +527,21 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             public boolean onMove(@NonNull final RecyclerView recyclerView,
                                   @NonNull final RecyclerView.ViewHolder source,
                                   @NonNull final RecyclerView.ViewHolder target) {
-                if (source.getItemViewType() != target.getItemViewType()
-                        || itemListAdapter == null) {
-                    // Allow swap LocalBookmarkPlaylistItemHolder and
-                    // RemoteBookmarkPlaylistItemHolder.
-                    if (!(
-                            (
-                                    (source instanceof LocalBookmarkPlaylistItemHolder)
-                                            || (source instanceof RemoteBookmarkPlaylistItemHolder)
-                            )
-                                    && (
-                                    (target instanceof LocalBookmarkPlaylistItemHolder)
-                                            || (target instanceof RemoteBookmarkPlaylistItemHolder)
-                            )
-                    )) {
-                        return false;
-                    }
+
+                // Allow swap LocalBookmarkPlaylistItemHolder and RemoteBookmarkPlaylistItemHolder.
+                if (itemListAdapter == null
+                        || source.getItemViewType() != target.getItemViewType()
+                        && !(
+                        (
+                                (source instanceof LocalBookmarkPlaylistItemHolder)
+                                        || (source instanceof RemoteBookmarkPlaylistItemHolder)
+                        )
+                                && (
+                                (target instanceof LocalBookmarkPlaylistItemHolder)
+                                        || (target instanceof RemoteBookmarkPlaylistItemHolder)
+                        ))
+                ) {
+                    return false;
                 }
 
                 final int sourceIndex = source.getBindingAdapterPosition();
@@ -569,6 +566,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             @Override
             public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
                                  final int swipeDir) {
+                // Do nothing.
             }
         };
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
index 697ef1072..16130009b 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalBookmarkPlaylistItemHolder.java
@@ -9,12 +9,10 @@ import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
 import org.schabi.newpipe.local.LocalItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.PicassoHelper;
 
 import java.time.format.DateTimeFormatter;
 
-public class LocalBookmarkPlaylistItemHolder extends PlaylistItemHolder {
+public class LocalBookmarkPlaylistItemHolder extends LocalPlaylistItemHolder {
     private final View itemHandleView;
 
     public LocalBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
@@ -37,13 +35,6 @@ public class LocalBookmarkPlaylistItemHolder extends PlaylistItemHolder {
         }
         final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
 
-        itemTitleView.setText(item.name);
-        itemStreamCountView.setText(Localization.localizeStreamCountMini(
-                itemStreamCountView.getContext(), item.streamCount));
-        itemUploaderView.setVisibility(View.INVISIBLE);
-
-        PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
-
         itemHandleView.setOnTouchListener(getOnTouchListener(item));
 
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
index 2cfc94463..50bbcd566 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java
@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.holder;
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
 import org.schabi.newpipe.local.LocalItemBuilder;
@@ -16,7 +15,7 @@ import java.time.format.DateTimeFormatter;
 public class LocalPlaylistItemHolder extends PlaylistItemHolder {
 
     public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
-        this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
+        super(infoItemBuilder, parent);
     }
 
     LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java
index 345223b08..6d61d1e08 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemoteBookmarkPlaylistItemHolder.java
@@ -1,6 +1,5 @@
 package org.schabi.newpipe.local.holder;
 
-import android.text.TextUtils;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -8,15 +7,12 @@ import android.view.ViewGroup;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
-import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.local.LocalItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.PicassoHelper;
 
 import java.time.format.DateTimeFormatter;
 
-public class RemoteBookmarkPlaylistItemHolder extends PlaylistItemHolder {
+public class RemoteBookmarkPlaylistItemHolder extends RemotePlaylistItemHolder {
     private final View itemHandleView;
 
     public RemoteBookmarkPlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
@@ -39,19 +35,6 @@ public class RemoteBookmarkPlaylistItemHolder extends PlaylistItemHolder {
         }
         final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
 
-        itemTitleView.setText(item.getName());
-        itemStreamCountView.setText(Localization.localizeStreamCountMini(
-                itemStreamCountView.getContext(), item.getStreamCount()));
-        // Here is where the uploader name is set in the bookmarked playlists library
-        if (!TextUtils.isEmpty(item.getUploader())) {
-            itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(),
-                    NewPipe.getNameOfService(item.getServiceId())));
-        } else {
-            itemUploaderView.setText(NewPipe.getNameOfService(item.getServiceId()));
-        }
-
-        PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
-
         itemHandleView.setOnTouchListener(getOnTouchListener(item));
 
         super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
index d2059bfed..865b2c4f7 100644
--- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java
@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.holder;
 import android.text.TextUtils;
 import android.view.ViewGroup;
 
-import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.LocalItem;
 import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
 import org.schabi.newpipe.extractor.NewPipe;
@@ -18,7 +17,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
 
     public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
                                     final ViewGroup parent) {
-        this(infoItemBuilder, R.layout.list_playlist_mini_item, parent);
+        super(infoItemBuilder, parent);
     }
 
     RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final int layoutId,

From bd1aae8d66d68ca511740a7b3765b513354c4e6e Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Sat, 16 Apr 2022 12:44:24 +0800
Subject: [PATCH 009/141] Fix sonar warning

---
 .../newpipe/local/bookmark/BookmarkFragment.java | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 14100ef81..b19107817 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -446,12 +446,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 final Long databaseIndex = displayIndexInDatabase.remove(key);
 
                 // The database index should not be null because inserting new item into database
-                // is not dealt here. NullPointerException has occurred once, but I can't reproduce
-                // it. Enhance robustness here.
-                if (databaseIndex != null) {
-                    if (databaseIndex != i) {
-                        localItemsUpdate.add((PlaylistMetadataEntry) item);
-                    }
+                // is not handled here. NullPointerException has occurred once, but I can't
+                // reproduce it. Enhance robustness here.
+                if (databaseIndex != null && databaseIndex != i) {
+                    localItemsUpdate.add((PlaylistMetadataEntry) item);
                 }
             } else if (item instanceof PlaylistRemoteEntity) {
                 ((PlaylistRemoteEntity) item).setDisplayIndex(i);
@@ -461,10 +459,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                         LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM);
                 final Long databaseIndex = displayIndexInDatabase.remove(key);
 
-                if (databaseIndex != null) {
-                    if (databaseIndex != i) {
-                        remoteItemsUpdate.add((PlaylistRemoteEntity) item);
-                    }
+                if (databaseIndex != null && databaseIndex != i) {
+                    remoteItemsUpdate.add((PlaylistRemoteEntity) item);
                 }
             }
         }

From bb5390d63a6737ce7b24add776e9e017ac544ae6 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Sun, 17 Apr 2022 14:53:02 +0800
Subject: [PATCH 010/141] Reuse DebounceSaver

---
 .../database/playlist/PlaylistLocalItem.java  |  8 +-
 .../local/bookmark/BookmarkFragment.java      | 81 +++++++------------
 .../local/playlist/LocalPlaylistFragment.java | 73 ++++++-----------
 .../schabi/newpipe/util/DebounceSavable.java  | 15 ++++
 .../schabi/newpipe/util/DebounceSaver.java    | 68 ++++++++++++++++
 5 files changed, 142 insertions(+), 103 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java
 create mode 100644 app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 47c6dd617..0e7beba41 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -17,15 +17,15 @@ public interface PlaylistLocalItem extends LocalItem {
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
 
-        // Merge localPlaylists and remotePlaylists by displayIndex.
-        // If two items have the same displayIndex, sort them in CASE_INSENSITIVE_ORDER.
+        // Merge localPlaylists and remotePlaylists by display index.
+        // If two items have the same display index, sort them in CASE_INSENSITIVE_ORDER.
         // This algorithm is similar to the merge operation in merge sort.
 
         final List<PlaylistLocalItem> result = new ArrayList<>(
                 localPlaylists.size() + remotePlaylists.size());
         final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
 
-        // The data from database may not be in the displayIndex order
+        // The data from database may not be in the display index order
         Collections.sort(localPlaylists,
                 Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
         Collections.sort(remotePlaylists,
@@ -58,7 +58,7 @@ public interface PlaylistLocalItem extends LocalItem {
                         final List<PlaylistLocalItem> itemsWithSameIndex) {
         if (!itemsWithSameIndex.isEmpty()
                 && itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
-            // The new item has a different displayIndex, add previous items with same
+            // The new item has a different display index, add previous items with same
             // index to the result.
             addItemsWithSameIndex(result, itemsWithSameIndex);
             itemsWithSameIndex.clear();
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index b19107817..ceeb980c7 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -35,6 +35,8 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
 import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
+import org.schabi.newpipe.util.DebounceSavable;
+import org.schabi.newpipe.util.DebounceSaver;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
 
@@ -42,7 +44,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import icepick.State;
@@ -50,12 +51,10 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.core.Flowable;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.subjects.PublishSubject;
 
-public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
+public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void>
+        implements DebounceSavable {
 
-    // Save the list 10 seconds after the last change occurred
-    private static final long SAVE_DEBOUNCE_MILLIS = 10000;
     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
     protected Parcelable itemsListState;
@@ -66,12 +65,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     private RemotePlaylistManager remotePlaylistManager;
     private ItemTouchHelper itemTouchHelper;
 
-    private PublishSubject<Long> debouncedSaveSignal;
-
-    /* Has the playlist been fully loaded from db */
+    /* Have the bookmarked playlists been fully loaded from db */
     private AtomicBoolean isLoadingComplete;
-    /* Has the playlist been modified (e.g. items reordered or deleted) */
-    private AtomicBoolean isModified;
+
+    private DebounceSaver debounceSaver;
 
     // Map from (uid, local/remote item) to the saved display index in the database.
     private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
@@ -91,9 +88,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         remotePlaylistManager = new RemotePlaylistManager(database);
         disposables = new CompositeDisposable();
 
-        debouncedSaveSignal = PublishSubject.create();
         isLoadingComplete = new AtomicBoolean();
-        isModified = new AtomicBoolean();
+        debounceSaver = new DebounceSaver(10000, this);
 
         displayIndexInDatabase = new HashMap<>();
     }
@@ -183,9 +179,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     public void startLoading(final boolean forceLoad) {
         super.startLoading(forceLoad);
 
-        disposables.add(getDebouncedSaver());
+        if (debounceSaver != null) {
+            disposables.add(debounceSaver.getDebouncedSaver());
+            debounceSaver.setIsModified(false);
+        }
         isLoadingComplete.set(false);
-        isModified.set(false);
 
         Flowable.combineLatest(localPlaylistManager.getPlaylists(),
                 remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
@@ -225,21 +223,20 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (debouncedSaveSignal != null) {
-            debouncedSaveSignal.onComplete();
+        if (debounceSaver != null) {
+            debounceSaver.getDebouncedSaveSignal().onComplete();
         }
         if (disposables != null) {
             disposables.dispose();
         }
 
-        debouncedSaveSignal = null;
+        debounceSaver = null;
         disposables = null;
         localPlaylistManager = null;
         remotePlaylistManager = null;
         itemsListState = null;
 
         isLoadingComplete = null;
-        isModified = null;
         displayIndexInDatabase = null;
     }
 
@@ -263,7 +260,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
             @Override
             public void onNext(final List<PlaylistLocalItem> subscriptions) {
-                if (isModified == null || !isModified.get()) {
+                if (debounceSaver == null || !debounceSaver.getIsModified()) {
                     checkDisplayIndexModified(subscriptions);
                     handleResult(subscriptions);
                     isLoadingComplete.set(true);
@@ -346,11 +343,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
         itemListAdapter.removeItem(item);
 
-        saveChanges();
+        debounceSaver.saveChanges();
     }
 
     private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
-        if (isModified != null && isModified.get()) {
+        if (debounceSaver != null && debounceSaver.getIsModified()) {
             return;
         }
 
@@ -358,8 +355,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
         // If the display index does not match actual index in the list, update the display index.
         // This may happen when a new list is created
-        // or on the first run after database update
-        // or displayIndex is not continuous for some reason.
+        // or on the first run after database migration
+        // or display index is not continuous for some reason
+        // or the user changes the display index.
         boolean isDisplayIndexModified = false;
         for (int i = 0; i < result.size(); i++) {
             final PlaylistLocalItem item = result.get(i);
@@ -388,40 +386,19 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
 
         if (isDisplayIndexModified) {
-            saveChanges();
+            debounceSaver.saveChanges();
         }
     }
 
-    private void saveChanges() {
-        if (isModified == null || debouncedSaveSignal == null) {
-            return;
-        }
-
-        isModified.set(true);
-        debouncedSaveSignal.onNext(System.currentTimeMillis());
-    }
-
-    private Disposable getDebouncedSaver() {
-        if (debouncedSaveSignal == null) {
-            return Disposable.empty();
-        }
-
-        return debouncedSaveSignal
-                .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(ignored -> saveImmediate(), throwable ->
-                        showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
-                                "Debounced saver")));
-    }
-
-    private void saveImmediate() {
+    @Override
+    public void saveImmediate() {
         if (itemListAdapter == null) {
             return;
         }
 
         // List must be loaded and modified in order to save
-        if (isLoadingComplete == null || isModified == null
-                || !isLoadingComplete.get() || !isModified.get()) {
+        if (isLoadingComplete == null || debounceSaver == null
+                || !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
             Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
                     + "is not loaded or playlists not modified");
             return;
@@ -485,8 +462,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                         remoteItemsUpdate, remoteItemsDeleteUid)
                                 .observeOn(AndroidSchedulers.mainThread())
                                 .subscribe(() -> {
-                                            if (isModified != null) {
-                                                isModified.set(false);
+                                            if (debounceSaver != null) {
+                                                debounceSaver.setIsModified(false);
                                             }
                                         },
                                         throwable -> showError(new ErrorInfo(throwable,
@@ -544,7 +521,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
                 if (isSwapped) {
-                    saveChanges();
+                    debounceSaver.saveChanges();
                 }
                 return isSwapped;
             }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index 0eb56d716..d929c93a5 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -46,6 +46,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.player.MainPlayer.PlayerType;
 import org.schabi.newpipe.player.playqueue.PlayQueue;
 import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
+import org.schabi.newpipe.util.DebounceSavable;
+import org.schabi.newpipe.util.DebounceSaver;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
@@ -55,7 +57,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import icepick.State;
@@ -64,11 +65,9 @@ import io.reactivex.rxjava3.core.Flowable;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import io.reactivex.rxjava3.disposables.Disposable;
 import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.PublishSubject;
 
-public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
-    // Save the list 10 seconds after the last change occurred
-    private static final long SAVE_DEBOUNCE_MILLIS = 10000;
+public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void>
+        implements DebounceSavable {
     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
     protected Long playlistId;
@@ -85,13 +84,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
     private LocalPlaylistManager playlistManager;
     private Subscription databaseSubscription;
 
-    private PublishSubject<Long> debouncedSaveSignal;
     private CompositeDisposable disposables;
 
     /* Has the playlist been fully loaded from db */
     private AtomicBoolean isLoadingComplete;
-    /* Has the playlist been modified (e.g. items reordered or deleted) */
-    private AtomicBoolean isModified;
+
+    private DebounceSaver debounceSaver;
+
     /* Is the playlist currently being processed to remove watched videos */
     private boolean isRemovingWatched = false;
 
@@ -109,12 +108,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
     public void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
-        debouncedSaveSignal = PublishSubject.create();
 
         disposables = new CompositeDisposable();
 
         isLoadingComplete = new AtomicBoolean();
-        isModified = new AtomicBoolean();
+        debounceSaver = new DebounceSaver(10000, this);
     }
 
     @Override
@@ -220,10 +218,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         if (disposables != null) {
             disposables.clear();
         }
-        disposables.add(getDebouncedSaver());
+
+        if (debounceSaver != null) {
+            disposables.add(debounceSaver.getDebouncedSaver());
+            debounceSaver.setIsModified(false);
+        }
 
         isLoadingComplete.set(false);
-        isModified.set(false);
 
         playlistManager.getPlaylistStreams(playlistId)
                 .onBackpressureLatest()
@@ -285,19 +286,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (debouncedSaveSignal != null) {
-            debouncedSaveSignal.onComplete();
+        if (debounceSaver != null) {
+            debounceSaver.getDebouncedSaveSignal().onComplete();
         }
         if (disposables != null) {
             disposables.dispose();
         }
 
-        debouncedSaveSignal = null;
+        debounceSaver = null;
         playlistManager = null;
         disposables = null;
 
         isLoadingComplete = null;
-        isModified = null;
     }
 
     ///////////////////////////////////////////////////////////////////////////
@@ -321,7 +321,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
             @Override
             public void onNext(final List<PlaylistStreamEntry> streams) {
                 // Skip handling the result after it has been modified
-                if (isModified == null || !isModified.get()) {
+                if (debounceSaver == null || !debounceSaver.getIsModified()) {
                     handleResult(streams);
                     isLoadingComplete.set(true);
                 }
@@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
 
                     itemListAdapter.clearStreamItemList();
                     itemListAdapter.addItems(notWatchedItems);
-                    saveChanges();
+                    debounceSaver.saveChanges();
 
 
                     if (thumbnailVideoRemoved) {
@@ -609,39 +609,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         }
 
         setVideoCount(itemListAdapter.getItemsList().size());
-        saveChanges();
+        debounceSaver.saveChanges();
     }
 
-    private void saveChanges() {
-        if (isModified == null || debouncedSaveSignal == null) {
-            return;
-        }
-
-        isModified.set(true);
-        debouncedSaveSignal.onNext(System.currentTimeMillis());
-    }
-
-    private Disposable getDebouncedSaver() {
-        if (debouncedSaveSignal == null) {
-            return Disposable.empty();
-        }
-
-        return debouncedSaveSignal
-                .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(ignored -> saveImmediate(), throwable ->
-                        showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
-                                "Debounced saver")));
-    }
-
-    private void saveImmediate() {
+    @Override
+    public void saveImmediate() {
         if (playlistManager == null || itemListAdapter == null) {
             return;
         }
 
         // List must be loaded and modified in order to save
-        if (isLoadingComplete == null || isModified == null
-                || !isLoadingComplete.get() || !isModified.get()) {
+        if (isLoadingComplete == null || debounceSaver == null
+                || !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
             Log.w(TAG, "Attempting to save playlist when local playlist "
                     + "is not loaded or not modified: playlist id=[" + playlistId + "]");
             return;
@@ -664,8 +643,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(
                         () -> {
-                            if (isModified != null) {
-                                isModified.set(false);
+                            if (debounceSaver != null) {
+                                debounceSaver.setIsModified(false);
                             }
                         },
                         throwable -> showError(new ErrorInfo(throwable,
@@ -708,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
                 if (isSwapped) {
-                    saveChanges();
+                    debounceSaver.saveChanges();
                 }
                 return isSwapped;
             }
diff --git a/app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java b/app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java
new file mode 100644
index 000000000..189dce9c6
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java
@@ -0,0 +1,15 @@
+package org.schabi.newpipe.util;
+
+import org.schabi.newpipe.error.ErrorInfo;
+
+public interface DebounceSavable {
+
+    /**
+     * Execute operations to save the data. <br>
+     * Must set {@link DebounceSaver#setIsModified(boolean)} false in this method manually
+     * after the data has been saved.
+     */
+    void saveImmediate();
+
+    void showError(ErrorInfo errorInfo);
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java b/app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
new file mode 100644
index 000000000..b17d7a29c
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
@@ -0,0 +1,68 @@
+package org.schabi.newpipe.util;
+
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.UserAction;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.subjects.PublishSubject;
+
+public class DebounceSaver {
+
+    private final long saveDebounceMillis;
+
+    private final PublishSubject<Long> debouncedSaveSignal;
+
+    private final DebounceSavable debounceSavable;
+
+    // Has the object been modified
+    private final AtomicBoolean isModified;
+
+
+    /**
+     * Creates a new {@code DebounceSaver}.
+     *
+     * @param saveDebounceMillis    Save the object milliseconds later after the last change
+     *                              occurred.
+     * @param debounceSavable       The object containing data to be saved.
+     */
+    public DebounceSaver(final long saveDebounceMillis, final DebounceSavable debounceSavable) {
+        this.saveDebounceMillis = saveDebounceMillis;
+        debouncedSaveSignal = PublishSubject.create();
+        this.debounceSavable = debounceSavable;
+        this.isModified = new AtomicBoolean();
+    }
+
+    public boolean getIsModified() {
+        return isModified.get();
+    }
+
+    public void setIsModified(final boolean isModified) {
+        this.isModified.set(isModified);
+    }
+
+    public PublishSubject<Long> getDebouncedSaveSignal() {
+        return debouncedSaveSignal;
+    }
+
+    public Disposable getDebouncedSaver() {
+        return debouncedSaveSignal
+                .debounce(saveDebounceMillis, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(ignored -> debounceSavable.saveImmediate(), throwable ->
+                        debounceSavable.showError(new ErrorInfo(throwable,
+                                UserAction.SOMETHING_ELSE, "Debounced saver")));
+    }
+
+    public void saveChanges() {
+        if (isModified == null || debouncedSaveSignal == null) {
+            return;
+        }
+
+        isModified.set(true);
+        debouncedSaveSignal.onNext(System.currentTimeMillis());
+    }
+}

From 6526ff1612ad430325a5bf8af7b425055306a0e6 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Sun, 17 Apr 2022 20:20:20 +0800
Subject: [PATCH 011/141] Add tests

---
 .../newpipe/database/DatabaseMigrationTest.kt |  92 ++++++++++++++-
 .../local/bookmark/BookmarkFragment.java      |   3 +-
 .../playlist/PlaylistLocalItemTest.java       | 105 ++++++++++++++++++
 3 files changed, 197 insertions(+), 3 deletions(-)
 create mode 100644 app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java

diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index 6d05a45bf..73b6313db 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -13,6 +13,8 @@ import org.junit.Assert.assertNull
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.schabi.newpipe.database.playlist.model.PlaylistEntity
+import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
 import org.schabi.newpipe.extractor.stream.StreamType
 
 @RunWith(AndroidJUnit4::class)
@@ -21,13 +23,17 @@ class DatabaseMigrationTest {
         private const val DEFAULT_SERVICE_ID = 0
         private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
         private const val DEFAULT_TITLE = "Test Title"
+        private const val DEFAULT_NAME = "Test Name"
         private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
         private const val DEFAULT_DURATION = 480L
         private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
         private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
 
-        private const val DEFAULT_SECOND_SERVICE_ID = 0
+        private const val DEFAULT_SECOND_SERVICE_ID = 1
         private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
+
+        private const val DEFAULT_THIRD_SERVICE_ID = 2
+        private const val DEFAULT_THIRD_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
     }
 
     @get:Rule
@@ -123,6 +129,90 @@ class DatabaseMigrationTest {
         assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
     }
 
+    @Test
+    fun migrateDatabaseFrom5to6() {
+        val databaseInV5 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_5)
+
+        val localUid1: Long
+        val localUid2: Long
+        val remoteUid1: Long
+        val remoteUid2: Long
+        databaseInV5.run {
+            localUid1 = insert(
+                "playlists", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("name", DEFAULT_NAME + "1")
+                    put("thumbnail_url", DEFAULT_THUMBNAIL)
+                }
+            )
+            localUid2 = insert(
+                "playlists", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("name", DEFAULT_NAME + "2")
+                    put("thumbnail_url", DEFAULT_THUMBNAIL)
+                }
+            )
+            delete(
+                "playlists", "uid = ?",
+                Array(1) { localUid1 }
+            )
+            remoteUid1 = insert(
+                "remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SERVICE_ID)
+                    put("url", DEFAULT_URL)
+                }
+            )
+            remoteUid2 = insert(
+                "remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SECOND_SERVICE_ID)
+                    put("url", DEFAULT_SECOND_URL)
+                }
+            )
+            delete(
+                "remote_playlists", "uid = ?",
+                Array(1) { remoteUid2 }
+            )
+            close()
+        }
+
+        testHelper.runMigrationsAndValidate(
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
+            true, Migrations.MIGRATION_5_6
+        )
+
+        val migratedDatabaseV6 = getMigratedDatabase()
+        var localListFromDB = migratedDatabaseV6.playlistDAO().all.blockingFirst()
+        var remoteListFromDB = migratedDatabaseV6.playlistRemoteDAO().all.blockingFirst()
+
+        assertEquals(1, localListFromDB.size)
+        assertEquals(localUid2, localListFromDB[0].uid)
+        assertEquals(0, localListFromDB[0].displayIndex)
+        assertEquals(1, remoteListFromDB.size)
+        assertEquals(remoteUid1, remoteListFromDB[0].uid)
+        assertEquals(0, remoteListFromDB[0].displayIndex)
+
+        val localUid3 = migratedDatabaseV6.playlistDAO().insert(
+            PlaylistEntity(DEFAULT_NAME + "3", DEFAULT_THUMBNAIL, -1)
+        )
+        val remoteUid3 = migratedDatabaseV6.playlistRemoteDAO().insert(
+            PlaylistRemoteEntity(
+                DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
+                DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
+            )
+        )
+
+        localListFromDB = migratedDatabaseV6.playlistDAO().all.blockingFirst()
+        remoteListFromDB = migratedDatabaseV6.playlistRemoteDAO().all.blockingFirst()
+        assertEquals(2, localListFromDB.size)
+        assertEquals(localUid3, localListFromDB[1].uid)
+        assertEquals(-1, localListFromDB[1].displayIndex)
+        assertEquals(2, remoteListFromDB.size)
+        assertEquals(remoteUid3, remoteListFromDB[1].uid)
+        assertEquals(-1, remoteListFromDB[1].displayIndex)
+    }
+
     private fun getMigratedDatabase(): AppDatabase {
         val database: AppDatabase = Room.databaseBuilder(
             ApplicationProvider.getApplicationContext(),
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index ceeb980c7..a79719525 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -385,7 +385,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             }
         }
 
-        if (isDisplayIndexModified) {
+        if (debounceSaver != null && isDisplayIndexModified) {
             debounceSaver.saveChanges();
         }
     }
@@ -588,4 +588,3 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 .show();
     }
 }
-
diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
new file mode 100644
index 000000000..e5f717144
--- /dev/null
+++ b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
@@ -0,0 +1,105 @@
+package org.schabi.newpipe.database.playlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaylistLocalItemTest {
+    @Test
+    public void emptyPlaylists() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        final List<PlaylistLocalItem> mergedPlaylists =
+                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+
+        assertEquals(0, mergedPlaylists.size());
+    }
+
+    @Test
+    public void onlyLocalPlaylists() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 2, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 0, 1));
+        final List<PlaylistLocalItem> mergedPlaylists =
+                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+
+        assertEquals(3, mergedPlaylists.size());
+        assertEquals(0, mergedPlaylists.get(0).getDisplayIndex());
+        assertEquals(1, mergedPlaylists.get(1).getDisplayIndex());
+        assertEquals(2, mergedPlaylists.get(2).getDisplayIndex());
+    }
+
+    @Test
+    public void onlyRemotePlaylists() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                1, "name1", "url1", "", "", 2, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                2, "name2", "url2", "", "", 1, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                3, "name3", "url3", "", "", 0, 1L));
+        final List<PlaylistLocalItem> mergedPlaylists =
+                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+
+        assertEquals(3, mergedPlaylists.size());
+        assertEquals(0, mergedPlaylists.get(0).getDisplayIndex());
+        assertEquals(1, mergedPlaylists.get(1).getDisplayIndex());
+        assertEquals(2, mergedPlaylists.get(2).getDisplayIndex());
+    }
+
+    @Test
+    public void sameIndexWithDifferentName() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 0, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                1, "name3", "url1", "", "", 0, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                2, "name4", "url2", "", "", 1, 1L));
+        final List<PlaylistLocalItem> mergedPlaylists =
+                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+
+        assertEquals(4, mergedPlaylists.size());
+        assertTrue(mergedPlaylists.get(0) instanceof PlaylistMetadataEntry);
+        assertEquals("name1", ((PlaylistMetadataEntry) mergedPlaylists.get(0)).name);
+        assertTrue(mergedPlaylists.get(1) instanceof PlaylistRemoteEntity);
+        assertEquals("name3", ((PlaylistRemoteEntity) mergedPlaylists.get(1)).getName());
+        assertTrue(mergedPlaylists.get(2) instanceof PlaylistMetadataEntry);
+        assertEquals("name2", ((PlaylistMetadataEntry) mergedPlaylists.get(2)).name);
+        assertTrue(mergedPlaylists.get(3) instanceof PlaylistRemoteEntity);
+        assertEquals("name4", ((PlaylistRemoteEntity) mergedPlaylists.get(3)).getName());
+    }
+
+    @Test
+    public void sameNameWithDifferentIndex() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 3, 1));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                1, "name1", "url1", "", "", 0, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                2, "name2", "url2", "", "", 2, 1L));
+        final List<PlaylistLocalItem> mergedPlaylists =
+                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+
+        assertEquals(4, mergedPlaylists.size());
+        assertTrue(mergedPlaylists.get(0) instanceof PlaylistRemoteEntity);
+        assertEquals("name1", ((PlaylistRemoteEntity) mergedPlaylists.get(0)).getName());
+        assertTrue(mergedPlaylists.get(1) instanceof PlaylistMetadataEntry);
+        assertEquals("name1", ((PlaylistMetadataEntry) mergedPlaylists.get(1)).name);
+        assertTrue(mergedPlaylists.get(2) instanceof PlaylistRemoteEntity);
+        assertEquals("name2", ((PlaylistRemoteEntity) mergedPlaylists.get(2)).getName());
+        assertTrue(mergedPlaylists.get(3) instanceof PlaylistMetadataEntry);
+        assertEquals("name2", ((PlaylistMetadataEntry) mergedPlaylists.get(3)).name);
+    }
+}

From d32490a4be288348a8377c84dbf8c82253240bf6 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Wed, 11 May 2022 16:47:34 +0800
Subject: [PATCH 012/141] Create sub-package and default interval for
 DebounceSaver & sort playlists in db

---
 .../database/playlist/PlaylistLocalItem.java      |  6 +-----
 .../database/playlist/dao/PlaylistRemoteDAO.java  |  5 +++++
 .../newpipe/local/bookmark/BookmarkFragment.java  | 10 +++++-----
 .../local/playlist/LocalPlaylistFragment.java     |  6 +++---
 .../local/playlist/RemotePlaylistManager.java     |  4 ++++
 .../newpipe/settings/SelectPlaylistFragment.java  |  4 ++--
 .../util/{ => debounce}/DebounceSavable.java      |  2 +-
 .../util/{ => debounce}/DebounceSaver.java        | 15 ++++++++++++++-
 8 files changed, 35 insertions(+), 17 deletions(-)
 rename app/src/main/java/org/schabi/newpipe/util/{ => debounce}/DebounceSavable.java (89%)
 rename app/src/main/java/org/schabi/newpipe/util/{ => debounce}/DebounceSaver.java (81%)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 0e7beba41..8b01a636a 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -16,6 +16,7 @@ public interface PlaylistLocalItem extends LocalItem {
     static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
+        // The playlists from the database must be in the display index order.
 
         // Merge localPlaylists and remotePlaylists by display index.
         // If two items have the same display index, sort them in CASE_INSENSITIVE_ORDER.
@@ -25,11 +26,6 @@ public interface PlaylistLocalItem extends LocalItem {
                 localPlaylists.size() + remotePlaylists.size());
         final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
 
-        // The data from database may not be in the display index order
-        Collections.sort(localPlaylists,
-                Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
-        Collections.sort(remotePlaylists,
-                Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
         int i = 0;
         int j = 0;
         while (i < localPlaylists.size()) {
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
index ade857464..8118bc40f 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
@@ -11,6 +11,7 @@ import java.util.List;
 
 import io.reactivex.rxjava3.core.Flowable;
 
+import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_DISPLAY_INDEX;
 import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
 import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
 import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
@@ -39,6 +40,10 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
             + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
     Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
 
+    @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
+     + " ORDER BY " + REMOTE_PLAYLIST_DISPLAY_INDEX)
+    Flowable<List<PlaylistRemoteEntity>> getDisplayIndexOrderedPlaylists();
+
     @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
             + " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
             + "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index a79719525..b0833dd9c 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -35,8 +35,8 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
 import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
 import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
-import org.schabi.newpipe.util.DebounceSavable;
-import org.schabi.newpipe.util.DebounceSaver;
+import org.schabi.newpipe.util.debounce.DebounceSavable;
+import org.schabi.newpipe.util.debounce.DebounceSaver;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
 
@@ -89,7 +89,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         disposables = new CompositeDisposable();
 
         isLoadingComplete = new AtomicBoolean();
-        debounceSaver = new DebounceSaver(10000, this);
+        debounceSaver = new DebounceSaver(this);
 
         displayIndexInDatabase = new HashMap<>();
     }
@@ -185,8 +185,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
         isLoadingComplete.set(false);
 
-        Flowable.combineLatest(localPlaylistManager.getPlaylists(),
-                remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
+        Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
+                remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
                 .onBackpressureLatest()
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(getPlaylistsSubscriber());
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index d929c93a5..1b8302cac 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -46,8 +46,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.player.MainPlayer.PlayerType;
 import org.schabi.newpipe.player.playqueue.PlayQueue;
 import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
-import org.schabi.newpipe.util.DebounceSavable;
-import org.schabi.newpipe.util.DebounceSaver;
+import org.schabi.newpipe.util.debounce.DebounceSavable;
+import org.schabi.newpipe.util.debounce.DebounceSaver;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
@@ -112,7 +112,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         disposables = new CompositeDisposable();
 
         isLoadingComplete = new AtomicBoolean();
-        debounceSaver = new DebounceSaver(10000, this);
+        debounceSaver = new DebounceSaver(this);
     }
 
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
index 1dbd726ae..45d4ef644 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
@@ -26,6 +26,10 @@ public class RemotePlaylistManager {
         return playlistRemoteTable.getAll().subscribeOn(Schedulers.io());
     }
 
+    public Flowable<List<PlaylistRemoteEntity>> getDisplayIndexOrderedPlaylists() {
+        return playlistRemoteTable.getDisplayIndexOrderedPlaylists().subscribeOn(Schedulers.io());
+    }
+
     public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) {
         return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl())
                 .subscribeOn(Schedulers.io());
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index e8491d52c..cc47c3f1c 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -90,8 +90,8 @@ public class SelectPlaylistFragment extends DialogFragment {
         final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database);
         final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database);
 
-        disposable = Flowable.combineLatest(localPlaylistManager.getPlaylists(),
-                remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
+        disposable = Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
+                remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(this::displayPlaylists, this::onError);
     }
diff --git a/app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSavable.java
similarity index 89%
rename from app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java
rename to app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSavable.java
index 189dce9c6..acc515dd6 100644
--- a/app/src/main/java/org/schabi/newpipe/util/DebounceSavable.java
+++ b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSavable.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.debounce;
 
 import org.schabi.newpipe.error.ErrorInfo;
 
diff --git a/app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
similarity index 81%
rename from app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
rename to app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
index b17d7a29c..367174ab7 100644
--- a/app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
+++ b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.debounce;
 
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
@@ -21,6 +21,9 @@ public class DebounceSaver {
     // Has the object been modified
     private final AtomicBoolean isModified;
 
+    // Default 10 seconds
+    private static final long DEFAULT_SAVE_DEBOUNCE_MILLIS = 10000;
+
 
     /**
      * Creates a new {@code DebounceSaver}.
@@ -36,6 +39,16 @@ public class DebounceSaver {
         this.isModified = new AtomicBoolean();
     }
 
+    /**
+     * Creates a new {@code DebounceSaver}. Save the object 10 seconds later after the last change
+     * occurred.
+     *
+     * @param debounceSavable       The object containing data to be saved.
+     */
+    public DebounceSaver(final DebounceSavable debounceSavable) {
+        this(DEFAULT_SAVE_DEBOUNCE_MILLIS, debounceSavable);
+    }
+
     public boolean getIsModified() {
         return isModified.get();
     }

From ba394a7ab4d3a0c1ee705565c8543e9160d9ebb5 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Wed, 11 May 2022 18:08:14 +0800
Subject: [PATCH 013/141] Update test and Javadoc

---
 .../database/playlist/PlaylistLocalItem.java  | 27 ++++++++++--
 .../playlist/PlaylistLocalItemTest.java       | 41 +++++++++++++++----
 2 files changed, 56 insertions(+), 12 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 8b01a636a..352d12d6b 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -13,13 +13,34 @@ public interface PlaylistLocalItem extends LocalItem {
 
     long getDisplayIndex();
 
+    /**
+     * Merge localPlaylists and remotePlaylists by the display index.
+     * If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
+     *
+     * @param localPlaylists  local playlists in the display index order
+     * @param remotePlaylists remote playlists in the display index order
+     * @return merged playlists
+     */
     static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
-        // The playlists from the database must be in the display index order.
 
-        // Merge localPlaylists and remotePlaylists by display index.
-        // If two items have the same display index, sort them in CASE_INSENSITIVE_ORDER.
+        for (int i = 1; i < localPlaylists.size(); i++) {
+            if (localPlaylists.get(i).getDisplayIndex()
+                    < localPlaylists.get(i - 1).getDisplayIndex()) {
+                throw new IllegalArgumentException(
+                        "localPlaylists is not in the display index order");
+            }
+        }
+
+        for (int i = 1; i < remotePlaylists.size(); i++) {
+            if (remotePlaylists.get(i).getDisplayIndex()
+                    < remotePlaylists.get(i - 1).getDisplayIndex()) {
+                throw new IllegalArgumentException(
+                        "remotePlaylists is not in the display index order");
+            }
+        }
+
         // This algorithm is similar to the merge operation in merge sort.
 
         final List<PlaylistLocalItem> result = new ArrayList<>(
diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
index e5f717144..98f611037 100644
--- a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
+++ b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
@@ -24,16 +24,26 @@ public class PlaylistLocalItemTest {
     public void onlyLocalPlaylists() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 2, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 0, 1));
         localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 0, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 3, 1));
         final List<PlaylistLocalItem> mergedPlaylists =
                 PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
 
         assertEquals(3, mergedPlaylists.size());
         assertEquals(0, mergedPlaylists.get(0).getDisplayIndex());
         assertEquals(1, mergedPlaylists.get(1).getDisplayIndex());
-        assertEquals(2, mergedPlaylists.get(2).getDisplayIndex());
+        assertEquals(3, mergedPlaylists.get(2).getDisplayIndex());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidLocalPlaylists() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 2, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 0, 1));
+        PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
     }
 
     @Test
@@ -41,18 +51,31 @@ public class PlaylistLocalItemTest {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
         remotePlaylists.add(new PlaylistRemoteEntity(
-                1, "name1", "url1", "", "", 2, 1L));
+                1, "name1", "url1", "", "", 1, 1L));
         remotePlaylists.add(new PlaylistRemoteEntity(
-                2, "name2", "url2", "", "", 1, 1L));
+                2, "name2", "url2", "", "", 2, 1L));
         remotePlaylists.add(new PlaylistRemoteEntity(
-                3, "name3", "url3", "", "", 0, 1L));
+                3, "name3", "url3", "", "", 4, 1L));
         final List<PlaylistLocalItem> mergedPlaylists =
                 PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
 
         assertEquals(3, mergedPlaylists.size());
-        assertEquals(0, mergedPlaylists.get(0).getDisplayIndex());
-        assertEquals(1, mergedPlaylists.get(1).getDisplayIndex());
-        assertEquals(2, mergedPlaylists.get(2).getDisplayIndex());
+        assertEquals(1, mergedPlaylists.get(0).getDisplayIndex());
+        assertEquals(2, mergedPlaylists.get(1).getDisplayIndex());
+        assertEquals(4, mergedPlaylists.get(2).getDisplayIndex());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidRemotePlaylists() {
+        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
+        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                1, "name1", "url1", "", "", 1, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                2, "name2", "url2", "", "", 3, 1L));
+        remotePlaylists.add(new PlaylistRemoteEntity(
+                3, "name3", "url3", "", "", 0, 1L));
+        PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
     }
 
     @Test

From 9ecef6f01103fc3aa0c719dd7abea4169d06c7d6 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 23 Jun 2022 19:20:16 +0800
Subject: [PATCH 014/141] Add abstract methods in PlaylistLocalItem & rename
 setIsModified

---
 .../database/playlist/PlaylistLocalItem.java  |  4 ++++
 .../playlist/PlaylistMetadataEntry.java       | 14 +++++++++--
 .../playlist/model/PlaylistEntity.java        |  4 ++--
 .../playlist/model/PlaylistRemoteEntity.java  |  2 ++
 .../local/bookmark/BookmarkFragment.java      | 23 +++++++++----------
 .../local/dialog/PlaylistAppendDialog.java    |  4 ++--
 .../local/playlist/LocalPlaylistFragment.java |  4 ++--
 .../settings/SelectPlaylistFragment.java      |  2 +-
 .../newpipe/util/debounce/DebounceSaver.java  |  4 ++--
 9 files changed, 38 insertions(+), 23 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 352d12d6b..3d58d3f7c 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -13,6 +13,10 @@ public interface PlaylistLocalItem extends LocalItem {
 
     long getDisplayIndex();
 
+    long getUid();
+
+    void setDisplayIndex(long displayIndex);
+
     /**
      * Merge localPlaylists and remotePlaylists by the display index.
      * If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
index ff80049a3..f1ead0fa4 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistMetadataEntry.java
@@ -11,13 +11,13 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
     public static final String PLAYLIST_STREAM_COUNT = "streamCount";
 
     @ColumnInfo(name = PLAYLIST_ID)
-    public final long uid;
+    private final long uid;
     @ColumnInfo(name = PLAYLIST_NAME)
     public final String name;
     @ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
     public final String thumbnailUrl;
     @ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
-    public long displayIndex;
+    private long displayIndex;
     @ColumnInfo(name = PLAYLIST_STREAM_COUNT)
     public final long streamCount;
 
@@ -44,4 +44,14 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
     public long getDisplayIndex() {
         return displayIndex;
     }
+
+    @Override
+    public long getUid() {
+        return uid;
+    }
+
+    @Override
+    public void setDisplayIndex(final long displayIndex) {
+        this.displayIndex = displayIndex;
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index 272e8a5bc..508b55508 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -41,10 +41,10 @@ public class PlaylistEntity {
 
     @Ignore
     public PlaylistEntity(final PlaylistMetadataEntry item) {
-        this.uid = item.uid;
+        this.uid = item.getUid();
         this.name = item.name;
         this.thumbnailUrl = item.thumbnailUrl;
-        this.displayIndex = item.displayIndex;
+        this.displayIndex = item.getDisplayIndex();
     }
 
     public long getUid() {
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
index adea2738b..82baed82c 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
@@ -105,6 +105,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
                 && TextUtils.equals(getUploader(), info.getUploaderName());
     }
 
+    @Override
     public long getUid() {
         return uid;
     }
@@ -158,6 +159,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
         return displayIndex;
     }
 
+    @Override
     public void setDisplayIndex(final long displayIndex) {
         this.displayIndex = displayIndex;
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index b0833dd9c..e9cf83239 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -139,7 +139,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
                 if (selectedItem instanceof PlaylistMetadataEntry) {
                     final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
-                    NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid,
+                    NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.getUid(),
                             entry.name);
 
                 } else if (selectedItem instanceof PlaylistRemoteEntity) {
@@ -181,7 +181,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
         if (debounceSaver != null) {
             disposables.add(debounceSaver.getDebouncedSaver());
-            debounceSaver.setIsModified(false);
+            debounceSaver.setNoChangesToSave();
         }
         isLoadingComplete.set(false);
 
@@ -371,16 +371,15 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             // Save the index read from the database.
             if (item instanceof PlaylistMetadataEntry) {
 
-                displayIndexInDatabase.put(new Pair<>(((PlaylistMetadataEntry) item).uid,
+                displayIndexInDatabase.put(new Pair<>(item.getUid(),
                         LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM), item.getDisplayIndex());
-                ((PlaylistMetadataEntry) item).displayIndex = i;
+                item.setDisplayIndex(i);
 
             } else if (item instanceof PlaylistRemoteEntity) {
 
-                displayIndexInDatabase.put(new Pair<>(((PlaylistRemoteEntity) item).getUid(),
-                                LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM),
-                        item.getDisplayIndex());
-                ((PlaylistRemoteEntity) item).setDisplayIndex(i);
+                displayIndexInDatabase.put(new Pair<>(item.getUid(),
+                        LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM), item.getDisplayIndex());
+                item.setDisplayIndex(i);
 
             }
         }
@@ -415,9 +414,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             final LocalItem item = items.get(i);
 
             if (item instanceof PlaylistMetadataEntry) {
-                ((PlaylistMetadataEntry) item).displayIndex = i;
+                ((PlaylistMetadataEntry) item).setDisplayIndex(i);
 
-                final Long uid = ((PlaylistMetadataEntry) item).uid;
+                final Long uid = ((PlaylistMetadataEntry) item).getUid();
                 final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
                         LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM);
                 final Long databaseIndex = displayIndexInDatabase.remove(key);
@@ -463,7 +462,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                                 .observeOn(AndroidSchedulers.mainThread())
                                 .subscribe(() -> {
                                             if (debounceSaver != null) {
-                                                debounceSaver.setIsModified(false);
+                                                debounceSaver.setNoChangesToSave();
                                             }
                                         },
                                         throwable -> showError(new ErrorInfo(throwable,
@@ -563,7 +562,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         builder.setView(dialogBinding.getRoot())
                 .setPositiveButton(R.string.rename_playlist, (dialog, which) ->
                         changeLocalPlaylistName(
-                                selectedItem.uid,
+                                selectedItem.getUid(),
                                 dialogBinding.dialogEditText.getText().toString()))
                 .setNegativeButton(R.string.cancel, null)
                 .setNeutralButton(R.string.delete, (dialog, which) -> {
diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
index 58a10af22..a778e6578 100644
--- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java
@@ -147,12 +147,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
 
         if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) {
             playlistDisposables.add(manager
-                    .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl())
+                    .changePlaylistThumbnail(playlist.getUid(), streams.get(0).getThumbnailUrl())
                     .observeOn(AndroidSchedulers.mainThread())
                     .subscribe(ignored -> successToast.show()));
         }
 
-        playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams)
+        playlistDisposables.add(manager.appendToPlaylist(playlist.getUid(), streams)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(ignored -> successToast.show()));
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index 1b8302cac..d129e658e 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -221,7 +221,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
 
         if (debounceSaver != null) {
             disposables.add(debounceSaver.getDebouncedSaver());
-            debounceSaver.setIsModified(false);
+            debounceSaver.setNoChangesToSave();
         }
 
         isLoadingComplete.set(false);
@@ -644,7 +644,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                 .subscribe(
                         () -> {
                             if (debounceSaver != null) {
-                                debounceSaver.setIsModified(false);
+                                debounceSaver.setNoChangesToSave();
                             }
                         },
                         throwable -> showError(new ErrorInfo(throwable,
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index cc47c3f1c..905a44fd1 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -118,7 +118,7 @@ public class SelectPlaylistFragment extends DialogFragment {
 
             if (selectedItem instanceof PlaylistMetadataEntry) {
                 final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
-                onSelectedListener.onLocalPlaylistSelected(entry.uid, entry.name);
+                onSelectedListener.onLocalPlaylistSelected(entry.getUid(), entry.name);
 
             } else if (selectedItem instanceof PlaylistRemoteEntity) {
                 final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
diff --git a/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
index 367174ab7..911e978ff 100644
--- a/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
+++ b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
@@ -53,8 +53,8 @@ public class DebounceSaver {
         return isModified.get();
     }
 
-    public void setIsModified(final boolean isModified) {
-        this.isModified.set(isModified);
+    public void setNoChangesToSave() {
+        isModified.set(false);
     }
 
     public PublishSubject<Long> getDebouncedSaveSignal() {

From 4e401bc059ae1ede7f452fcfc68dd6d8c8987500 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 23 Jun 2022 20:36:21 +0800
Subject: [PATCH 015/141] Update playlists in parallel

---
 .../local/bookmark/BookmarkFragment.java      | 21 +++++++------------
 1 file changed, 8 insertions(+), 13 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index e9cf83239..9b93cc3e6 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -454,21 +454,16 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
         // 1. Update local playlists
         // 2. Update remote playlists
-        // 3. Set isModified false
+        // 3. Set NoChangesToSave
         disposables.add(localPlaylistManager.updatePlaylists(localItemsUpdate, localItemsDeleteUid)
+                .mergeWith(remotePlaylistManager.updatePlaylists(
+                        remoteItemsUpdate, remoteItemsDeleteUid))
                 .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(() -> disposables.add(remotePlaylistManager.updatePlaylists(
-                        remoteItemsUpdate, remoteItemsDeleteUid)
-                                .observeOn(AndroidSchedulers.mainThread())
-                                .subscribe(() -> {
-                                            if (debounceSaver != null) {
-                                                debounceSaver.setNoChangesToSave();
-                                            }
-                                        },
-                                        throwable -> showError(new ErrorInfo(throwable,
-                                                UserAction.REQUESTED_BOOKMARK,
-                                                "Saving playlist"))
-                                )),
+                .subscribe(() -> {
+                            if (debounceSaver != null) {
+                                debounceSaver.setNoChangesToSave();
+                            }
+                        },
                         throwable -> showError(new ErrorInfo(throwable,
                                 UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
                 ));

From 898a936064f19830779b0bf22f7c75b4bd4dd4df Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 23 Jun 2022 23:19:59 +0800
Subject: [PATCH 016/141] Update index modification logic & redo sorting in the
 merge algorithm

---
 .../database/playlist/PlaylistLocalItem.java  | 23 ++---
 .../local/bookmark/BookmarkFragment.java      | 86 +++++++------------
 .../local/playlist/LocalPlaylistFragment.java |  6 +-
 .../newpipe/util/debounce/DebounceSaver.java  |  2 +-
 .../playlist/PlaylistLocalItemTest.java       | 23 -----
 5 files changed, 39 insertions(+), 101 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index 3d58d3f7c..4314b0f82 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -21,29 +21,18 @@ public interface PlaylistLocalItem extends LocalItem {
      * Merge localPlaylists and remotePlaylists by the display index.
      * If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
      *
-     * @param localPlaylists  local playlists in the display index order
-     * @param remotePlaylists remote playlists in the display index order
+     * @param localPlaylists  local playlists
+     * @param remotePlaylists remote playlists
      * @return merged playlists
      */
     static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
 
-        for (int i = 1; i < localPlaylists.size(); i++) {
-            if (localPlaylists.get(i).getDisplayIndex()
-                    < localPlaylists.get(i - 1).getDisplayIndex()) {
-                throw new IllegalArgumentException(
-                        "localPlaylists is not in the display index order");
-            }
-        }
-
-        for (int i = 1; i < remotePlaylists.size(); i++) {
-            if (remotePlaylists.get(i).getDisplayIndex()
-                    < remotePlaylists.get(i - 1).getDisplayIndex()) {
-                throw new IllegalArgumentException(
-                        "remotePlaylists is not in the display index order");
-            }
-        }
+        Collections.sort(localPlaylists,
+                Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
+        Collections.sort(remotePlaylists,
+                Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
 
         // This algorithm is similar to the merge operation in merge sort.
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 9b93cc3e6..200fde562 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -41,9 +41,7 @@ import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import icepick.State;
@@ -70,8 +68,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
     private DebounceSaver debounceSaver;
 
-    // Map from (uid, local/remote item) to the saved display index in the database.
-    private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
+    private List<Pair<Long, LocalItem.LocalItemType>> deletedItems;
 
     ///////////////////////////////////////////////////////////////////////////
     // Fragment LifeCycle - Creation
@@ -89,9 +86,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         disposables = new CompositeDisposable();
 
         isLoadingComplete = new AtomicBoolean();
-        debounceSaver = new DebounceSaver(this);
+        debounceSaver = new DebounceSaver(3000, this);
 
-        displayIndexInDatabase = new HashMap<>();
+        deletedItems = new ArrayList<>();
     }
 
     @Nullable
@@ -186,7 +183,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         isLoadingComplete.set(false);
 
         Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
-                remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
+                        remotePlaylistManager.getDisplayIndexOrderedPlaylists(),
+                        PlaylistLocalItem::merge)
                 .onBackpressureLatest()
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(getPlaylistsSubscriber());
@@ -237,7 +235,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         itemsListState = null;
 
         isLoadingComplete = null;
-        displayIndexInDatabase = null;
+        deletedItems = null;
     }
 
     ///////////////////////////////////////////////////////////////////////////
@@ -343,7 +341,15 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
         itemListAdapter.removeItem(item);
 
-        debounceSaver.saveChanges();
+        if (item instanceof PlaylistMetadataEntry) {
+            deletedItems.add(new Pair<>(item.getUid(),
+                    LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM));
+        } else if (item instanceof PlaylistRemoteEntity) {
+            deletedItems.add(new Pair<>(item.getUid(),
+                    LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM));
+        }
+
+        debounceSaver.setHasChangesToSave();
     }
 
     private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
@@ -351,9 +357,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             return;
         }
 
-        displayIndexInDatabase.clear();
-
-        // If the display index does not match actual index in the list, update the display index.
+        // Check if the display index does not match the actual index in the list.
         // This may happen when a new list is created
         // or on the first run after database migration
         // or display index is not continuous for some reason
@@ -363,29 +367,12 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             final PlaylistLocalItem item = result.get(i);
             if (item.getDisplayIndex() != i) {
                 isDisplayIndexModified = true;
-            }
-
-            // Updating display index in the item does not affect the value inserts into
-            // database, which will be recalculated during the database update. Updating
-            // display index in the item here is to determine whether it is recently modified.
-            // Save the index read from the database.
-            if (item instanceof PlaylistMetadataEntry) {
-
-                displayIndexInDatabase.put(new Pair<>(item.getUid(),
-                        LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM), item.getDisplayIndex());
-                item.setDisplayIndex(i);
-
-            } else if (item instanceof PlaylistRemoteEntity) {
-
-                displayIndexInDatabase.put(new Pair<>(item.getUid(),
-                        LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM), item.getDisplayIndex());
-                item.setDisplayIndex(i);
-
+                break;
             }
         }
 
         if (debounceSaver != null && isDisplayIndexModified) {
-            debounceSaver.saveChanges();
+            debounceSaver.setHasChangesToSave();
         }
     }
 
@@ -414,43 +401,28 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             final LocalItem item = items.get(i);
 
             if (item instanceof PlaylistMetadataEntry) {
-                ((PlaylistMetadataEntry) item).setDisplayIndex(i);
-
-                final Long uid = ((PlaylistMetadataEntry) item).getUid();
-                final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
-                        LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM);
-                final Long databaseIndex = displayIndexInDatabase.remove(key);
-
-                // The database index should not be null because inserting new item into database
-                // is not handled here. NullPointerException has occurred once, but I can't
-                // reproduce it. Enhance robustness here.
-                if (databaseIndex != null && databaseIndex != i) {
+                if (((PlaylistMetadataEntry) item).getDisplayIndex() != i) {
+                    ((PlaylistMetadataEntry) item).setDisplayIndex(i);
                     localItemsUpdate.add((PlaylistMetadataEntry) item);
                 }
             } else if (item instanceof PlaylistRemoteEntity) {
-                ((PlaylistRemoteEntity) item).setDisplayIndex(i);
-
-                final Long uid = ((PlaylistRemoteEntity) item).getUid();
-                final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
-                        LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM);
-                final Long databaseIndex = displayIndexInDatabase.remove(key);
-
-                if (databaseIndex != null && databaseIndex != i) {
+                if (((PlaylistRemoteEntity) item).getDisplayIndex() != i) {
+                    ((PlaylistRemoteEntity) item).setDisplayIndex(i);
                     remoteItemsUpdate.add((PlaylistRemoteEntity) item);
                 }
             }
         }
 
         // Find deleted items
-        for (final Pair<Long, LocalItem.LocalItemType> key : displayIndexInDatabase.keySet()) {
-            if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
-                localItemsDeleteUid.add(key.first);
-            } else if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
-                remoteItemsDeleteUid.add(key.first);
+        for (final Pair<Long, LocalItem.LocalItemType> item : deletedItems) {
+            if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
+                localItemsDeleteUid.add(item.first);
+            } else if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
+                remoteItemsDeleteUid.add(item.first);
             }
         }
 
-        displayIndexInDatabase.clear();
+        deletedItems.clear();
 
         // 1. Update local playlists
         // 2. Update remote playlists
@@ -515,7 +487,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
                 if (isSwapped) {
-                    debounceSaver.saveChanges();
+                    debounceSaver.setHasChangesToSave();
                 }
                 return isSwapped;
             }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index d129e658e..10e5aea15 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
 
                     itemListAdapter.clearStreamItemList();
                     itemListAdapter.addItems(notWatchedItems);
-                    debounceSaver.saveChanges();
+                    debounceSaver.setHasChangesToSave();
 
 
                     if (thumbnailVideoRemoved) {
@@ -609,7 +609,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         }
 
         setVideoCount(itemListAdapter.getItemsList().size());
-        debounceSaver.saveChanges();
+        debounceSaver.setHasChangesToSave();
     }
 
     @Override
@@ -687,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
                 if (isSwapped) {
-                    debounceSaver.saveChanges();
+                    debounceSaver.setHasChangesToSave();
                 }
                 return isSwapped;
             }
diff --git a/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
index 911e978ff..5bd5cdd55 100644
--- a/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
+++ b/app/src/main/java/org/schabi/newpipe/util/debounce/DebounceSaver.java
@@ -70,7 +70,7 @@ public class DebounceSaver {
                                 UserAction.SOMETHING_ELSE, "Debounced saver")));
     }
 
-    public void saveChanges() {
+    public void setHasChangesToSave() {
         if (isModified == null || debouncedSaveSignal == null) {
             return;
         }
diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
index 98f611037..ab6315d91 100644
--- a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
+++ b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
@@ -36,16 +36,6 @@ public class PlaylistLocalItemTest {
         assertEquals(3, mergedPlaylists.get(2).getDisplayIndex());
     }
 
-    @Test(expected = IllegalArgumentException.class)
-    public void invalidLocalPlaylists() {
-        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
-        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 2, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 0, 1));
-        PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
-    }
-
     @Test
     public void onlyRemotePlaylists() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
@@ -65,19 +55,6 @@ public class PlaylistLocalItemTest {
         assertEquals(4, mergedPlaylists.get(2).getDisplayIndex());
     }
 
-    @Test(expected = IllegalArgumentException.class)
-    public void invalidRemotePlaylists() {
-        final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
-        final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        remotePlaylists.add(new PlaylistRemoteEntity(
-                1, "name1", "url1", "", "", 1, 1L));
-        remotePlaylists.add(new PlaylistRemoteEntity(
-                2, "name2", "url2", "", "", 3, 1L));
-        remotePlaylists.add(new PlaylistRemoteEntity(
-                3, "name3", "url3", "", "", 0, 1L));
-        PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
-    }
-
     @Test
     public void sameIndexWithDifferentName() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();

From 8ad7bf60d7ed3bcde5bda0d601ae5d920c042eb3 Mon Sep 17 00:00:00 2001
From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com>
Date: Thu, 23 Jun 2022 23:31:56 +0800
Subject: [PATCH 017/141] Delete saveImmediate warnings & add comments

---
 .../org/schabi/newpipe/local/bookmark/BookmarkFragment.java    | 3 +--
 .../schabi/newpipe/local/playlist/LocalPlaylistFragment.java   | 2 --
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 200fde562..4be4838ec 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -66,6 +66,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     /* Have the bookmarked playlists been fully loaded from db */
     private AtomicBoolean isLoadingComplete;
 
+    /* Gives enough time to avoid interrupting user sorting operations */
     private DebounceSaver debounceSaver;
 
     private List<Pair<Long, LocalItem.LocalItemType>> deletedItems;
@@ -385,8 +386,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         // List must be loaded and modified in order to save
         if (isLoadingComplete == null || debounceSaver == null
                 || !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
-            Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
-                    + "is not loaded or playlists not modified");
             return;
         }
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index 10e5aea15..5ec511660 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -621,8 +621,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         // List must be loaded and modified in order to save
         if (isLoadingComplete == null || debounceSaver == null
                 || !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
-            Log.w(TAG, "Attempting to save playlist when local playlist "
-                    + "is not loaded or not modified: playlist id=[" + playlistId + "]");
             return;
         }
 

From 90f0809029a257f62c442eede1735e2386e8aed1 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Wed, 16 Aug 2023 21:24:55 +0200
Subject: [PATCH 018/141] Trim search string and remove duplicate records from
 the database

Co-authored-by:  Yingwei Zheng <dtcxzyw@qq.com>
---
 .../8.json                                    | 737 ++++++++++++++++++
 .../newpipe/database/DatabaseMigrationTest.kt |  61 +-
 .../org/schabi/newpipe/NewPipeDatabase.java   |   3 +-
 .../schabi/newpipe/database/AppDatabase.java  |   4 +-
 .../schabi/newpipe/database/Migrations.java   |  10 +
 .../fragments/list/search/SearchFragment.java |  42 +-
 6 files changed, 842 insertions(+), 15 deletions(-)
 create mode 100644 app/schemas/org.schabi.newpipe.database.AppDatabase/8.json

diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/8.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/8.json
new file mode 100644
index 000000000..d4a89567b
--- /dev/null
+++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/8.json
@@ -0,0 +1,737 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 8,
+    "identityHash": "012fc8e7ad3333f1597347f34e76a513",
+    "entities": [
+      {
+        "tableName": "subscriptions",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "avatarUrl",
+            "columnName": "avatar_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "subscriberCount",
+            "columnName": "subscriber_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "notificationMode",
+            "columnName": "notification_mode",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_subscriptions_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "search_history",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "creationDate",
+            "columnName": "creation_date",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "search",
+            "columnName": "search",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_search_history_search",
+            "unique": false,
+            "columnNames": [
+              "search"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "streams",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "title",
+            "columnName": "title",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "streamType",
+            "columnName": "stream_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "duration",
+            "columnName": "duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uploader",
+            "columnName": "uploader",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uploaderUrl",
+            "columnName": "uploader_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnail_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "viewCount",
+            "columnName": "view_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "textualUploadDate",
+            "columnName": "textual_upload_date",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "uploadDate",
+            "columnName": "upload_date",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "isUploadDateApproximation",
+            "columnName": "is_upload_date_approximation",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_streams_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "stream_history",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accessDate",
+            "columnName": "access_date",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "repeatCount",
+            "columnName": "repeat_count",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "stream_id",
+            "access_date"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_stream_history_stream_id",
+            "unique": false,
+            "columnNames": [
+              "stream_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "stream_state",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "progressMillis",
+            "columnName": "progress_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "stream_id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "playlists",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "isThumbnailPermanent",
+            "columnName": "is_thumbnail_permanent",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "thumbnailStreamId",
+            "columnName": "thumbnail_stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_playlists_name",
+            "unique": false,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "playlist_stream_join",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "playlistUid",
+            "columnName": "playlist_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "streamUid",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "index",
+            "columnName": "join_index",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "playlist_id",
+            "join_index"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_playlist_stream_join_playlist_id_join_index",
+            "unique": true,
+            "columnNames": [
+              "playlist_id",
+              "join_index"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
+          },
+          {
+            "name": "index_playlist_stream_join_stream_id",
+            "unique": false,
+            "columnNames": [
+              "stream_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "playlists",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "playlist_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "remote_playlists",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceId",
+            "columnName": "service_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "thumbnailUrl",
+            "columnName": "thumbnail_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "uploader",
+            "columnName": "uploader",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "streamCount",
+            "columnName": "stream_count",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_remote_playlists_name",
+            "unique": false,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
+          },
+          {
+            "name": "index_remote_playlists_service_id_url",
+            "unique": true,
+            "columnNames": [
+              "service_id",
+              "url"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "feed",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "streamId",
+            "columnName": "stream_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "stream_id",
+            "subscription_id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_feed_subscription_id",
+            "unique": false,
+            "columnNames": [
+              "subscription_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "streams",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "stream_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "feed_group",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "icon",
+            "columnName": "icon_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sortOrder",
+            "columnName": "sort_order",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_feed_group_sort_order",
+            "unique": false,
+            "columnNames": [
+              "sort_order"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "feed_group_subscription_join",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "feedGroupId",
+            "columnName": "group_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "group_id",
+            "subscription_id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_feed_group_subscription_join_subscription_id",
+            "unique": false,
+            "columnNames": [
+              "subscription_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "feed_group",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "group_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          },
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "feed_last_updated",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "subscriptionId",
+            "columnName": "subscription_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastUpdated",
+            "columnName": "last_updated",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "subscription_id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "subscriptions",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "subscription_id"
+            ],
+            "referencedColumns": [
+              "uid"
+            ]
+          }
+        ]
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '012fc8e7ad3333f1597347f34e76a513')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index 88e737205..cd048ac44 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -8,6 +8,7 @@ import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNull
 import org.junit.Rule
 import org.junit.Test
@@ -25,8 +26,11 @@ class DatabaseMigrationTest {
         private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
         private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
 
-        private const val DEFAULT_SECOND_SERVICE_ID = 0
+        private const val DEFAULT_SECOND_SERVICE_ID = 1
         private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
+
+        private const val DEFAULT_SEARCH1 = " abc "
+        private const val DEFAULT_SEARCH2 = " abc"
     }
 
     @get:Rule
@@ -106,6 +110,11 @@ class DatabaseMigrationTest {
             Migrations.MIGRATION_6_7
         )
 
+        testHelper.runMigrationsAndValidate(
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
+            true, Migrations.MIGRATION_5_6
+        )
+
         val migratedDatabaseV3 = getMigratedDatabase()
         val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
 
@@ -140,6 +149,56 @@ class DatabaseMigrationTest {
         assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
     }
 
+    @Test
+    fun migrateDatabaseFrom5to6() {
+        val databaseInV5 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_5)
+
+        databaseInV5.run {
+            insert(
+                "search_history", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SERVICE_ID)
+                    put("search", DEFAULT_SEARCH1)
+                }
+            )
+            insert(
+                "search_history", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SERVICE_ID)
+                    put("search", DEFAULT_SEARCH2)
+                }
+            )
+            insert(
+                "search_history", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SECOND_SERVICE_ID)
+                    put("search", DEFAULT_SEARCH1)
+                }
+            )
+            insert(
+                "search_history", SQLiteDatabase.CONFLICT_FAIL,
+                ContentValues().apply {
+                    put("service_id", DEFAULT_SECOND_SERVICE_ID)
+                    put("search", DEFAULT_SEARCH2)
+                }
+            )
+            close()
+        }
+
+        testHelper.runMigrationsAndValidate(
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
+            true, Migrations.MIGRATION_5_6
+        )
+
+        val migratedDatabaseV6 = getMigratedDatabase()
+        val listFromDB = migratedDatabaseV6.searchHistoryDAO().all.blockingFirst()
+
+        assertEquals(2, listFromDB.size)
+        assertEquals("abc", listFromDB[0].search)
+        assertEquals("abc", listFromDB[1].search)
+        assertNotEquals(listFromDB[0].serviceId, listFromDB[1].serviceId)
+    }
+
     private fun getMigratedDatabase(): AppDatabase {
         val database: AppDatabase = Room.databaseBuilder(
             ApplicationProvider.getApplicationContext(),
diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
index 856fbff8b..c4f9feba7 100644
--- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
@@ -7,6 +7,7 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6;
 import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
+import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
 
 import android.content.Context;
 import android.database.Cursor;
@@ -27,7 +28,7 @@ public final class NewPipeDatabase {
         return Room
                 .databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
                 .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
-                        MIGRATION_5_6, MIGRATION_6_7)
+                        MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8)
                 .build();
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
index 03e39cd43..d03823e66 100644
--- a/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
+++ b/app/src/main/java/org/schabi/newpipe/database/AppDatabase.java
@@ -1,6 +1,6 @@
 package org.schabi.newpipe.database;
 
-import static org.schabi.newpipe.database.Migrations.DB_VER_7;
+import static org.schabi.newpipe.database.Migrations.DB_VER_8;
 
 import androidx.room.Database;
 import androidx.room.RoomDatabase;
@@ -38,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
                 FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
                 FeedLastUpdatedEntity.class
         },
-        version = DB_VER_7
+        version = DB_VER_8
 )
 public abstract class AppDatabase extends RoomDatabase {
     public static final String DATABASE_NAME = "newpipe.db";
diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index 1886b87c2..65c5626a5 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -25,6 +25,7 @@ public final class Migrations {
     public static final int DB_VER_5 = 5;
     public static final int DB_VER_6 = 6;
     public static final int DB_VER_7 = 7;
+    public static final int DB_VER_8 = 8;
 
     private static final String TAG = Migrations.class.getName();
     public static final boolean DEBUG = MainActivity.DEBUG;
@@ -235,6 +236,15 @@ public final class Migrations {
         }
     };
 
+    public static final Migration MIGRATION_7_8 = new Migration(DB_VER_7, DB_VER_8) {
+        @Override
+        public void migrate(@NonNull final SupportSQLiteDatabase database) {
+            database.execSQL("DELETE FROM search_history WHERE id NOT IN (SELECT id FROM "
+                    + "(SELECT id FROM search_history GROUP BY trim(search), service_id) tmp)");
+            database.execSQL("UPDATE search_history SET search = trim(search)");
+        }
+    };
+
     private Migrations() {
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index 26a283229..87c48b69c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -384,6 +384,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
 
     @Override
     public void onSaveInstanceState(@NonNull final Bundle bundle) {
+        searchEditText.setText(searchEditText.getText().toString().trim());
         searchString = searchEditText != null
                 ? searchEditText.getText().toString()
                 : searchString;
@@ -396,8 +397,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
 
     @Override
     public void reloadContent() {
-        if (!TextUtils.isEmpty(searchString)
-                || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
+        if (!TextUtils.isEmpty(searchString) || (searchEditText != null
+                && TextUtils.getTrimmedLength(searchEditText.getText()) > 0)) {
             search(!TextUtils.isEmpty(searchString)
                     ? searchString
                     : searchEditText.getText().toString(), this.contentFilter, "");
@@ -494,7 +495,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         }
         searchEditText.setText(searchString);
 
-        if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
+        if (TextUtils.isEmpty(searchString)
+                || TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
             searchToolbarContainer.setTranslationX(100);
             searchToolbarContainer.setAlpha(0.0f);
             searchToolbarContainer.setVisibility(View.VISIBLE);
@@ -518,7 +520,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             if (DEBUG) {
                 Log.d(TAG, "onClick() called with: v = [" + v + "]");
             }
-            if (TextUtils.isEmpty(searchEditText.getText())) {
+            if (TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
                 NavigationHelper.gotoMainFragment(getFM());
                 return;
             }
@@ -580,9 +582,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             searchEditText.removeTextChangedListener(textWatcher);
         }
         textWatcher = new TextWatcher() {
+            private boolean isPastedText = false;
+
             @Override
             public void beforeTextChanged(final CharSequence s, final int start,
                                           final int count, final int after) {
+                isPastedText = TextUtils.isEmpty(s) && after > 1;
             }
 
             @Override
@@ -597,8 +602,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                     s.removeSpan(span);
                 }
 
-                final String newText = searchEditText.getText().toString();
+                final String newText = searchEditText.getText().toString().trim();
                 suggestionPublisher.onNext(newText);
+
+                if (isPastedText) {
+                    // trim pasted text
+                    searchEditText.setText(newText);
+                }
             }
         };
         searchEditText.addTextChangedListener(textWatcher);
@@ -613,6 +623,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                     } else if (event != null
                             && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                             || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
+                        searchEditText.setText(searchEditText.getText().toString().trim());
                         search(searchEditText.getText().toString(), new String[0], "");
                         return true;
                     }
@@ -717,9 +728,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                 .getRelatedSearches(query, similarQueryLimit, 25)
                 .toObservable()
                 .map(searchHistoryEntries ->
-                    searchHistoryEntries.stream()
-                            .map(entry -> new SuggestionItem(true, entry))
-                            .collect(Collectors.toList()));
+                        searchHistoryEntries.stream()
+                                .map(entry -> new SuggestionItem(true, entry))
+                                .collect(Collectors.toList()));
     }
 
     private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
@@ -786,12 +797,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                             } else if (listNotification.isOnError()
                                     && listNotification.getError() != null
                                     && !ExceptionUtils.isInterruptedCaused(
-                                            listNotification.getError())) {
+                                    listNotification.getError())) {
                                 showSnackBarError(new ErrorInfo(listNotification.getError(),
                                         UserAction.GET_SUGGESTIONS, searchString, serviceId));
                             }
                         }, throwable -> showSnackBarError(new ErrorInfo(
-                            throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
+                                throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
     }
 
     @Override
@@ -804,6 +815,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                         final String theSortFilter) {
         if (DEBUG) {
             Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
+            final String trimmedSearchString = theSearchString.trim();
+            if (!trimmedSearchString.equals(theSearchString)) {
+                Log.d(TAG, "The precondition is not satisfied. "
+                        + "\"theSearchString\" is not allowed to have leading or trailing spaces");
+            }
         }
         if (theSearchString.isEmpty()) {
             return;
@@ -839,7 +855,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(
-                        ignored -> { },
+                        ignored -> {
+                        },
                         throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
                                 theSearchString, serviceId))
                 ));
@@ -973,6 +990,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         }
 
         searchSuggestion = result.getSearchSuggestion();
+        if (searchSuggestion != null) {
+            searchSuggestion = searchSuggestion.trim();
+        }
         isCorrectedSearch = result.isCorrectedSearch();
 
         // List<MetaInfo> cannot be bundled without creating some containers

From 4af5b5f6f21c28b718224690a6239d13f019f032 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Wed, 16 Aug 2023 22:01:58 +0200
Subject: [PATCH 019/141] Fix database migration and string trimming

Co-authored-by:  Yingwei Zheng <dtcxzyw@qq.com>
---
 .../newpipe/database/DatabaseMigrationTest.kt | 23 +++++++++++--------
 .../schabi/newpipe/database/Migrations.java   |  4 ++--
 .../fragments/list/search/SearchFragment.java | 17 ++++----------
 3 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index cd048ac44..c0c608a58 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -111,8 +111,10 @@ class DatabaseMigrationTest {
         )
 
         testHelper.runMigrationsAndValidate(
-            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
-            true, Migrations.MIGRATION_5_6
+            AppDatabase.DATABASE_NAME,
+            Migrations.DB_VER_8,
+            true,
+            Migrations.MIGRATION_7_8
         )
 
         val migratedDatabaseV3 = getMigratedDatabase()
@@ -150,10 +152,13 @@ class DatabaseMigrationTest {
     }
 
     @Test
-    fun migrateDatabaseFrom5to6() {
-        val databaseInV5 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_5)
+    fun migrateDatabaseFrom7to8() {
+        val databaseInV7 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_7)
 
-        databaseInV5.run {
+        val defaultSearch1 = " abc "
+        val defaultSearch2 = " abc"
+
+        databaseInV7.run {
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
@@ -186,12 +191,12 @@ class DatabaseMigrationTest {
         }
 
         testHelper.runMigrationsAndValidate(
-            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
-            true, Migrations.MIGRATION_5_6
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_8,
+            true, Migrations.MIGRATION_7_8
         )
 
-        val migratedDatabaseV6 = getMigratedDatabase()
-        val listFromDB = migratedDatabaseV6.searchHistoryDAO().all.blockingFirst()
+        val migratedDatabaseV8 = getMigratedDatabase()
+        val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
 
         assertEquals(2, listFromDB.size)
         assertEquals("abc", listFromDB[0].search)
diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index 65c5626a5..4b1a34dd6 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -239,8 +239,8 @@ public final class Migrations {
     public static final Migration MIGRATION_7_8 = new Migration(DB_VER_7, DB_VER_8) {
         @Override
         public void migrate(@NonNull final SupportSQLiteDatabase database) {
-            database.execSQL("DELETE FROM search_history WHERE id NOT IN (SELECT id FROM "
-                    + "(SELECT id FROM search_history GROUP BY trim(search), service_id) tmp)");
+            database.execSQL("DELETE FROM search_history WHERE id NOT IN (SELECT id FROM (SELECT "
+                    + "MIN(id) as id FROM search_history GROUP BY trim(search), service_id ) tmp)");
             database.execSQL("UPDATE search_history SET search = trim(search)");
         }
     };
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index 87c48b69c..df4a47bc5 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -1,6 +1,7 @@
 package org.schabi.newpipe.fragments.list.search;
 
 import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
+import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
 import static org.schabi.newpipe.ktx.ViewUtils.animate;
 import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
 import static java.util.Arrays.asList;
@@ -398,7 +399,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
     @Override
     public void reloadContent() {
         if (!TextUtils.isEmpty(searchString) || (searchEditText != null
-                && TextUtils.getTrimmedLength(searchEditText.getText()) > 0)) {
+                && !isBlank(searchEditText.getText().toString()))) {
             search(!TextUtils.isEmpty(searchString)
                     ? searchString
                     : searchEditText.getText().toString(), this.contentFilter, "");
@@ -496,7 +497,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         searchEditText.setText(searchString);
 
         if (TextUtils.isEmpty(searchString)
-                || TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
+                || isBlank(searchEditText.getText().toString())) {
             searchToolbarContainer.setTranslationX(100);
             searchToolbarContainer.setAlpha(0.0f);
             searchToolbarContainer.setVisibility(View.VISIBLE);
@@ -520,7 +521,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             if (DEBUG) {
                 Log.d(TAG, "onClick() called with: v = [" + v + "]");
             }
-            if (TextUtils.getTrimmedLength(searchEditText.getText()) == 0) {
+            if (isBlank(searchEditText.getText().toString())) {
                 NavigationHelper.gotoMainFragment(getFM());
                 return;
             }
@@ -582,12 +583,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             searchEditText.removeTextChangedListener(textWatcher);
         }
         textWatcher = new TextWatcher() {
-            private boolean isPastedText = false;
-
             @Override
             public void beforeTextChanged(final CharSequence s, final int start,
                                           final int count, final int after) {
-                isPastedText = TextUtils.isEmpty(s) && after > 1;
             }
 
             @Override
@@ -604,11 +602,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
 
                 final String newText = searchEditText.getText().toString().trim();
                 suggestionPublisher.onNext(newText);
-
-                if (isPastedText) {
-                    // trim pasted text
-                    searchEditText.setText(newText);
-                }
             }
         };
         searchEditText.addTextChangedListener(textWatcher);
@@ -817,7 +810,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
             final String trimmedSearchString = theSearchString.trim();
             if (!trimmedSearchString.equals(theSearchString)) {
-                Log.d(TAG, "The precondition is not satisfied. "
+                Log.w(TAG, "The precondition is not satisfied. "
                         + "\"theSearchString\" is not allowed to have leading or trailing spaces");
             }
         }

From 881d04ba1ea6caa1cd88fe1b3cd0325f6a5ed89d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw@qq.com>
Date: Wed, 4 May 2022 20:20:19 +0800
Subject: [PATCH 020/141] Refactor database migration test and string trimming

---
 .../newpipe/database/DatabaseMigrationTest.kt | 11 +++----
 .../fragments/list/search/SearchFragment.java | 29 ++++++++++++-------
 2 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index c0c608a58..19053ba97 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -28,9 +28,6 @@ class DatabaseMigrationTest {
 
         private const val DEFAULT_SECOND_SERVICE_ID = 1
         private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
-
-        private const val DEFAULT_SEARCH1 = " abc "
-        private const val DEFAULT_SEARCH2 = " abc"
     }
 
     @get:Rule
@@ -163,28 +160,28 @@ class DatabaseMigrationTest {
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("service_id", DEFAULT_SERVICE_ID)
-                    put("search", DEFAULT_SEARCH1)
+                    put("search", defaultSearch1)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("service_id", DEFAULT_SERVICE_ID)
-                    put("search", DEFAULT_SEARCH2)
+                    put("search", defaultSearch2)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("service_id", DEFAULT_SECOND_SERVICE_ID)
-                    put("search", DEFAULT_SEARCH1)
+                    put("search", defaultSearch1)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("service_id", DEFAULT_SECOND_SERVICE_ID)
-                    put("search", DEFAULT_SEARCH2)
+                    put("search", defaultSearch2)
                 }
             )
             close()
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index df4a47bc5..07d41c160 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -385,9 +385,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
 
     @Override
     public void onSaveInstanceState(@NonNull final Bundle bundle) {
-        searchEditText.setText(searchEditText.getText().toString().trim());
         searchString = searchEditText != null
-                ? searchEditText.getText().toString()
+                ? getSearchEditString().trim()
                 : searchString;
         super.onSaveInstanceState(bundle);
     }
@@ -399,10 +398,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
     @Override
     public void reloadContent() {
         if (!TextUtils.isEmpty(searchString) || (searchEditText != null
-                && !isBlank(searchEditText.getText().toString()))) {
+                && !isSearchEditBlank())) {
             search(!TextUtils.isEmpty(searchString)
                     ? searchString
-                    : searchEditText.getText().toString(), this.contentFilter, "");
+                    : getSearchEditString(), this.contentFilter, "");
         } else {
             if (searchEditText != null) {
                 searchEditText.setText("");
@@ -497,7 +496,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         searchEditText.setText(searchString);
 
         if (TextUtils.isEmpty(searchString)
-                || isBlank(searchEditText.getText().toString())) {
+                || isSearchEditBlank()) {
             searchToolbarContainer.setTranslationX(100);
             searchToolbarContainer.setAlpha(0.0f);
             searchToolbarContainer.setVisibility(View.VISIBLE);
@@ -521,7 +520,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
             if (DEBUG) {
                 Log.d(TAG, "onClick() called with: v = [" + v + "]");
             }
-            if (isBlank(searchEditText.getText().toString())) {
+            if (isSearchEditBlank()) {
                 NavigationHelper.gotoMainFragment(getFM());
                 return;
             }
@@ -600,7 +599,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                     s.removeSpan(span);
                 }
 
-                final String newText = searchEditText.getText().toString().trim();
+                final String newText = getSearchEditString().trim();
                 suggestionPublisher.onNext(newText);
             }
         };
@@ -616,8 +615,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                     } else if (event != null
                             && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                             || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
-                        searchEditText.setText(searchEditText.getText().toString().trim());
-                        search(searchEditText.getText().toString(), new String[0], "");
+                        searchEditText.setText(getSearchEditString().trim());
+                        search(getSearchEditString(), new String[0], "");
                         return true;
                     }
                     return false;
@@ -692,7 +691,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                             .observeOn(AndroidSchedulers.mainThread())
                             .subscribe(
                                     howManyDeleted -> suggestionPublisher
-                                            .onNext(searchEditText.getText().toString()),
+                                            .onNext(getSearchEditString()),
                                     throwable -> showSnackBarError(new ErrorInfo(throwable,
                                             UserAction.DELETE_FROM_HISTORY,
                                             "Deleting item failed")));
@@ -942,6 +941,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         sortFilter = theSortFilter;
     }
 
+    private String getSearchEditString() {
+        return searchEditText.getText().toString();
+    }
+
+    private Boolean isSearchEditBlank() {
+        return isBlank(getSearchEditString());
+    }
+
     /*//////////////////////////////////////////////////////////////////////////
     // Suggestion Results
     //////////////////////////////////////////////////////////////////////////*/
@@ -1087,7 +1094,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(
                         howManyDeleted -> suggestionPublisher
-                                .onNext(searchEditText.getText().toString()),
+                                .onNext(getSearchEditString()),
                         throwable -> showSnackBarError(new ErrorInfo(throwable,
                                 UserAction.DELETE_FROM_HISTORY, "Deleting item failed")));
         disposables.add(onDelete);

From ef40ac7bb37a1e10214e48bd1b9ee82f88d69fd3 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw@qq.com>
Date: Thu, 5 May 2022 14:15:19 +0800
Subject: [PATCH 021/141] Fix a typo

---
 .../schabi/newpipe/fragments/list/search/SearchFragment.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index 07d41c160..ebdde2f95 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -945,7 +945,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         return searchEditText.getText().toString();
     }
 
-    private Boolean isSearchEditBlank() {
+    private boolean isSearchEditBlank() {
         return isBlank(getSearchEditString());
     }
 

From 15fd47c7f24c79b0be2e0e98f216c3140e88c8fb Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Wed, 16 Aug 2023 22:18:53 +0200
Subject: [PATCH 022/141] Apply review

---
 .../newpipe/database/DatabaseMigrationTest.kt    | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index 19053ba97..65f41d8fa 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -13,6 +13,7 @@ import org.junit.Assert.assertNull
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.schabi.newpipe.extractor.ServiceList
 import org.schabi.newpipe.extractor.stream.StreamType
 
 @RunWith(AndroidJUnit4::class)
@@ -26,7 +27,7 @@ class DatabaseMigrationTest {
         private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
         private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
 
-        private const val DEFAULT_SECOND_SERVICE_ID = 1
+        private const val DEFAULT_SECOND_SERVICE_ID = 0
         private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
     }
 
@@ -155,32 +156,37 @@ class DatabaseMigrationTest {
         val defaultSearch1 = " abc "
         val defaultSearch2 = " abc"
 
+        val serviceId = DEFAULT_SERVICE_ID // YouTube
+        // Use id different to YouTube because two searches with the same query
+        // but different service are considered not equal.
+        val otherServiceId = ServiceList.SoundCloud.serviceId
+
         databaseInV7.run {
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
-                    put("service_id", DEFAULT_SERVICE_ID)
+                    put("service_id", serviceId)
                     put("search", defaultSearch1)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
-                    put("service_id", DEFAULT_SERVICE_ID)
+                    put("service_id", serviceId)
                     put("search", defaultSearch2)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
-                    put("service_id", DEFAULT_SECOND_SERVICE_ID)
+                    put("service_id", otherServiceId)
                     put("search", defaultSearch1)
                 }
             )
             insert(
                 "search_history", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
-                    put("service_id", DEFAULT_SECOND_SERVICE_ID)
+                    put("service_id", otherServiceId)
                     put("search", defaultSearch2)
                 }
             )

From 9118ecd68fa8793df4db46958e4d63015d5e1c61 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Thu, 17 Aug 2023 16:51:31 +0200
Subject: [PATCH 023/141] Remove unnecessary debug warning and use JDoc instead

---
 .../fragments/list/search/SearchFragment.java | 43 +++++++++++--------
 1 file changed, 24 insertions(+), 19 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
index ebdde2f95..558f8df44 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java
@@ -802,40 +802,42 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
         // no-op
     }
 
-    private void search(final String theSearchString,
+    /**
+     * Perform a search.
+     * @param theSearchString the trimmed search string
+     * @param theContentFilter the content filter to use. FIXME: unused param
+     * @param theSortFilter FIXME: unused param
+     */
+    private void search(@NonNull final String theSearchString,
                         final String[] theContentFilter,
                         final String theSortFilter) {
         if (DEBUG) {
             Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
-            final String trimmedSearchString = theSearchString.trim();
-            if (!trimmedSearchString.equals(theSearchString)) {
-                Log.w(TAG, "The precondition is not satisfied. "
-                        + "\"theSearchString\" is not allowed to have leading or trailing spaces");
-            }
         }
         if (theSearchString.isEmpty()) {
             return;
         }
 
+        // Check if theSearchString is a URL which can be opened by NewPipe directly
+        // and open it if possible.
         try {
             final StreamingService streamingService = NewPipe.getServiceByUrl(theSearchString);
-            if (streamingService != null) {
-                showLoading();
-                disposables.add(Observable
-                        .fromCallable(() -> NavigationHelper.getIntentByLink(activity,
-                                streamingService, theSearchString))
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(intent -> {
-                            getFM().popBackStackImmediate();
-                            activity.startActivity(intent);
-                        }, throwable -> showTextError(getString(R.string.unsupported_url))));
-                return;
-            }
+            showLoading();
+            disposables.add(Observable
+                    .fromCallable(() -> NavigationHelper.getIntentByLink(activity,
+                            streamingService, theSearchString))
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(intent -> {
+                        getFM().popBackStackImmediate();
+                        activity.startActivity(intent);
+                    }, throwable -> showTextError(getString(R.string.unsupported_url))));
+            return;
         } catch (final Exception ignored) {
             // Exception occurred, it's not a url
         }
 
+        // prepare search
         lastSearchedString = this.searchString;
         this.searchString = theSearchString;
         infoListAdapter.clearStreamItemList();
@@ -844,6 +846,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                 searchBinding.searchMetaInfoSeparator, disposables);
         hideKeyboardSearch();
 
+        // store search query if search history is enabled
         disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(
@@ -852,6 +855,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
                         throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
                                 theSearchString, serviceId))
                 ));
+
+        // load search results
         suggestionPublisher.onNext(theSearchString);
         startLoading(false);
     }

From 5d101e7b88a824272ff78512c537432a489a1b31 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Mon, 24 Oct 2022 09:54:22 +1100
Subject: [PATCH 024/141] Added reset button but not working as intended.

---
 .../settings/AppearanceSettingsFragment.java   | 18 ++++++++++++++++++
 .../settings/ResetSettingsFragment.java        | 14 ++++++++++++++
 app/src/main/res/values/settings_keys.xml      |  2 ++
 app/src/main/res/values/strings.xml            |  1 +
 app/src/main/res/xml/main_settings.xml         |  8 ++++++++
 gradle/wrapper/gradle-wrapper.properties       |  2 ++
 settings.gradle                                | 10 +++++-----
 7 files changed, 50 insertions(+), 5 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java

diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index ef0e8670c..c8fa3e392 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -80,4 +80,22 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
             ActivityCompat.recreate(getActivity());
         }
     }
+
+    public void resetToDefault() {
+        final String themeKey = getString(R.string.theme_key);
+        final String startThemeKey = defaultPreferences
+                .getString(themeKey, getString(R.string.default_theme_value));
+        final String autoDeviceThemeKey = getString(R.string.auto_device_theme_key);
+        if (startThemeKey.equals(autoDeviceThemeKey)) {
+            applyThemeChange(startThemeKey, themeKey, autoDeviceThemeKey);
+            } else {
+            if (startThemeKey.equals(R.string.light_theme_key)) {
+                removePreference(getString(R.string.light_theme_key));
+            } else if (startThemeKey.equals(R.string.dark_theme_key)) {
+                removePreference(getString(R.string.dark_theme_key));
+            } else {
+                removePreference(getString(R.string.black_theme_key));
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
new file mode 100644
index 000000000..94936f6ca
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
@@ -0,0 +1,14 @@
+package org.schabi.newpipe.settings;
+
+import android.os.Bundle;
+
+public class ResetSettingsFragment extends BasePreferenceFragment {
+
+    private AppearanceSettingsFragment appearanceSettings;
+    @Override
+    public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
+        addPreferencesFromResourceRegistry();
+
+        appearanceSettings.resetToDefault();
+    }
+}
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 51abe14fb..de883420c 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -222,6 +222,8 @@
     <string name="prefer_original_audio_key">prefer_original_audio</string>
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
+    <!-- RESET ONLY -->
+    <string name="reset_pref_screen_key">reset_pref_screen_key</string>
 
     <!-- DEBUG ONLY -->
     <string name="debug_pref_screen_key">debug_pref_screen_key</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e5bbffaff..f83938139 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -151,6 +151,7 @@
     <string name="settings_category_history_title">History and cache</string>
     <string name="settings_category_appearance_title">Appearance</string>
     <string name="settings_category_debug_title">Debug</string>
+    <string name="settings_category_reset_title">Reset All Settings</string>
     <string name="settings_category_updates_title">Updates</string>
     <string name="settings_category_player_notification_title">Player notification</string>
     <string name="settings_category_player_notification_summary">Configure current playing stream notification</string>
diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml
index 1b1c17e85..a569f391c 100644
--- a/app/src/main/res/xml/main_settings.xml
+++ b/app/src/main/res/xml/main_settings.xml
@@ -53,4 +53,12 @@
         android:key="@string/debug_pref_screen_key"
         android:title="@string/settings_category_debug_title"
         app:iconSpaceReserved="false" />
+    <PreferenceScreen
+        android:fragment="org.schabi.newpipe.settings.ResetSettingsFragment"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:icon="@drawable/ic_replay"
+        android:key="@string/reset_pref_screen_key"
+        android:title="@string/settings_category_reset_title"
+        app:iconSpaceReserved="false" />
 </PreferenceScreen>
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2c3425d49..fcaba806a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,3 +1,4 @@
+#Fri Oct 21 13:32:17 AEDT 2022
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
@@ -5,3 +6,4 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
index 0338fde6c..a0aed9527 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,8 +4,8 @@ include ':app'
 // We assume, that NewPipe and NewPipe Extractor have the same parent directory.
 // If this is not the case, please change the path in includeBuild().
 
-//includeBuild('../NewPipeExtractor') {
-//    dependencySubstitution {
-//        substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
-//    }
-//}
+includeBuild('../NewPipeExtractor') {
+    dependencySubstitution {
+        substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
+    }
+}

From aa1847189ba9503636eb567ac64ca199c06e6c62 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Tue, 25 Oct 2022 13:18:31 +1100
Subject: [PATCH 025/141] Added reset button but slightly working as intended.

---
 .../settings/AppearanceSettingsFragment.java   | 18 ------------------
 .../settings/ResetSettingsFragment.java        | 13 +++++++++++--
 .../settings/SettingsResourceRegistry.java     |  1 +
 app/src/main/res/xml/reset_settings.xml        |  4 ++++
 4 files changed, 16 insertions(+), 20 deletions(-)
 create mode 100644 app/src/main/res/xml/reset_settings.xml

diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index c8fa3e392..ef0e8670c 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -80,22 +80,4 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
             ActivityCompat.recreate(getActivity());
         }
     }
-
-    public void resetToDefault() {
-        final String themeKey = getString(R.string.theme_key);
-        final String startThemeKey = defaultPreferences
-                .getString(themeKey, getString(R.string.default_theme_value));
-        final String autoDeviceThemeKey = getString(R.string.auto_device_theme_key);
-        if (startThemeKey.equals(autoDeviceThemeKey)) {
-            applyThemeChange(startThemeKey, themeKey, autoDeviceThemeKey);
-            } else {
-            if (startThemeKey.equals(R.string.light_theme_key)) {
-                removePreference(getString(R.string.light_theme_key));
-            } else if (startThemeKey.equals(R.string.dark_theme_key)) {
-                removePreference(getString(R.string.dark_theme_key));
-            } else {
-                removePreference(getString(R.string.black_theme_key));
-            }
-        }
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
index 94936f6ca..aa7f528c6 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
@@ -2,13 +2,22 @@ package org.schabi.newpipe.settings;
 
 import android.os.Bundle;
 
+import androidx.core.app.ActivityCompat;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.ThemeHelper;
+
 public class ResetSettingsFragment extends BasePreferenceFragment {
 
-    private AppearanceSettingsFragment appearanceSettings;
     @Override
     public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
         addPreferencesFromResourceRegistry();
 
-        appearanceSettings.resetToDefault();
+        // reset appearance to light theme
+        defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
+        defaultPreferences.edit().putString(getString(R.string.theme_key),
+                getString(R.string.light_theme_key)).apply();
+        ThemeHelper.setDayNightMode(requireContext(), "light_theme");
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
index b3d0741bb..20d6555a5 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
@@ -41,6 +41,7 @@ public final class SettingsResourceRegistry {
         add(UpdateSettingsFragment.class, R.xml.update_settings);
         add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
         add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_settings);
+        add(ResetSettingsFragment.class, R.xml.main_settings);
     }
 
     private SettingRegistryEntry add(
diff --git a/app/src/main/res/xml/reset_settings.xml b/app/src/main/res/xml/reset_settings.xml
new file mode 100644
index 000000000..624ed13ae
--- /dev/null
+++ b/app/src/main/res/xml/reset_settings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+</PreferenceScreen>
\ No newline at end of file

From 58517d1d27571b8f563656b42c472d9624ee2893 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Wed, 26 Oct 2022 17:09:53 +1100
Subject: [PATCH 026/141] Added reset button but working as intended for theme.

---
 .../settings/DebugSettingsFragment.java       | 13 +++++++++++
 .../settings/ResetSettingsFragment.java       | 23 -------------------
 .../settings/SettingsResourceRegistry.java    |  2 --
 app/src/main/res/values/settings_keys.xml     |  2 +-
 app/src/main/res/xml/debug_settings.xml       |  5 ++++
 app/src/main/res/xml/main_settings.xml        |  8 -------
 app/src/main/res/xml/reset_settings.xml       |  4 ----
 7 files changed, 19 insertions(+), 38 deletions(-)
 delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
 delete mode 100644 app/src/main/res/xml/reset_settings.xml

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 0f4c9765e..690220da7 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -10,7 +10,9 @@ import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
+import org.schabi.newpipe.util.Constants;
 import org.schabi.newpipe.util.PicassoHelper;
+import org.schabi.newpipe.util.ThemeHelper;
 
 import java.util.Optional;
 
@@ -35,6 +37,8 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
                 findPreference(getString(R.string.show_error_snackbar_key));
         final Preference createErrorNotificationPreference =
                 findPreference(getString(R.string.create_error_notification_key));
+        final Preference resetSettings =
+                findPreference(getString(R.string.reset_settings));
 
         assert allowHeapDumpingPreference != null;
         assert showMemoryLeaksPreference != null;
@@ -86,6 +90,15 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
                     new ErrorInfo(new RuntimeException(DUMMY), UserAction.UI_ERROR, DUMMY));
             return true;
         });
+
+        // reset appearance to light theme
+        resetSettings.setOnPreferenceClickListener(preference -> {
+            defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
+            defaultPreferences.edit().putString(getString(R.string.theme_key),
+                    getString(R.string.light_theme_key)).apply();
+            ThemeHelper.setDayNightMode(requireContext(), "light_theme");
+            return true;
+        });
     }
 
     /**
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
deleted file mode 100644
index aa7f528c6..000000000
--- a/app/src/main/java/org/schabi/newpipe/settings/ResetSettingsFragment.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.schabi.newpipe.settings;
-
-import android.os.Bundle;
-
-import androidx.core.app.ActivityCompat;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.util.Constants;
-import org.schabi.newpipe.util.ThemeHelper;
-
-public class ResetSettingsFragment extends BasePreferenceFragment {
-
-    @Override
-    public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
-        addPreferencesFromResourceRegistry();
-
-        // reset appearance to light theme
-        defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
-        defaultPreferences.edit().putString(getString(R.string.theme_key),
-                getString(R.string.light_theme_key)).apply();
-        ThemeHelper.setDayNightMode(requireContext(), "light_theme");
-    }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
index 20d6555a5..2fe4f42ed 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
@@ -30,7 +30,6 @@ public final class SettingsResourceRegistry {
 
     private SettingsResourceRegistry() {
         add(MainSettingsFragment.class, R.xml.main_settings).setSearchable(false);
-
         add(AppearanceSettingsFragment.class, R.xml.appearance_settings);
         add(ContentSettingsFragment.class, R.xml.content_settings);
         add(DebugSettingsFragment.class, R.xml.debug_settings).setSearchable(false);
@@ -41,7 +40,6 @@ public final class SettingsResourceRegistry {
         add(UpdateSettingsFragment.class, R.xml.update_settings);
         add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
         add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_settings);
-        add(ResetSettingsFragment.class, R.xml.main_settings);
     }
 
     private SettingRegistryEntry add(
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index de883420c..42d434900 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -223,7 +223,7 @@
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
     <!-- RESET ONLY -->
-    <string name="reset_pref_screen_key">reset_pref_screen_key</string>
+    <string name="reset_settings">reset_settings</string>
 
     <!-- DEBUG ONLY -->
     <string name="debug_pref_screen_key">debug_pref_screen_key</string>
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 84bb281f3..66836f138 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -71,4 +71,9 @@
         android:title="@string/create_error_notification"
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
+    <Preference
+        android:icon="@drawable/ic_replay"
+        android:key="@string/reset_settings"
+        android:title="@string/settings_category_reset_title"
+        app:iconSpaceReserved="false" />
 </PreferenceScreen>
diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml
index a569f391c..1b1c17e85 100644
--- a/app/src/main/res/xml/main_settings.xml
+++ b/app/src/main/res/xml/main_settings.xml
@@ -53,12 +53,4 @@
         android:key="@string/debug_pref_screen_key"
         android:title="@string/settings_category_debug_title"
         app:iconSpaceReserved="false" />
-    <PreferenceScreen
-        android:fragment="org.schabi.newpipe.settings.ResetSettingsFragment"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:icon="@drawable/ic_replay"
-        android:key="@string/reset_pref_screen_key"
-        android:title="@string/settings_category_reset_title"
-        app:iconSpaceReserved="false" />
 </PreferenceScreen>
diff --git a/app/src/main/res/xml/reset_settings.xml b/app/src/main/res/xml/reset_settings.xml
deleted file mode 100644
index 624ed13ae..000000000
--- a/app/src/main/res/xml/reset_settings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-
-</PreferenceScreen>
\ No newline at end of file

From 2103a040922227ad16c92a938eb5671d68e20569 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Wed, 26 Oct 2022 19:13:41 +1100
Subject: [PATCH 027/141] Cleaned up xml files.

---
 app/src/main/res/values/settings_keys.xml | 1 +
 app/src/main/res/xml/debug_settings.xml   | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 42d434900..ea283e62c 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -222,6 +222,7 @@
     <string name="prefer_original_audio_key">prefer_original_audio</string>
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
+
     <!-- RESET ONLY -->
     <string name="reset_settings">reset_settings</string>
 
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 66836f138..511bcd19a 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -72,7 +72,6 @@
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
     <Preference
-        android:icon="@drawable/ic_replay"
         android:key="@string/reset_settings"
         android:title="@string/settings_category_reset_title"
         app:iconSpaceReserved="false" />

From 076e9eee01ca3f55f56124ce407364182ff8278d Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 08:58:11 +1100
Subject: [PATCH 028/141] Added alert dialogue and restarts the app when
 resetting settings.

---
 .../settings/DebugSettingsFragment.java       | 33 +++++++++++++++----
 1 file changed, 26 insertions(+), 7 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 690220da7..56c88f730 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -1,5 +1,7 @@
 package org.schabi.newpipe.settings;
 
+import android.app.AlertDialog;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -10,9 +12,8 @@ import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
-import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PicassoHelper;
-import org.schabi.newpipe.util.ThemeHelper;
 
 import java.util.Optional;
 
@@ -91,12 +92,30 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
             return true;
         });
 
-        // reset appearance to light theme
+        // Resets all settings by deleting shared preference and restarting the app
+        // A dialogue will pop up to confirm if user intends to reset all settings
         resetSettings.setOnPreferenceClickListener(preference -> {
-            defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
-            defaultPreferences.edit().putString(getString(R.string.theme_key),
-                    getString(R.string.light_theme_key)).apply();
-            ThemeHelper.setDayNightMode(requireContext(), "light_theme");
+            final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            builder.setMessage("Resetting all settings will discard "
+                    + "all of your preferred settings and restarts the app. "
+                    + "Are you sure you want to do this?");
+            builder.setCancelable(true);
+            builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(final DialogInterface dialogInterface, final int i) {
+                    NavigationHelper.restartApp(getActivity());
+                }
+            });
+            builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(final DialogInterface dialogInterface, final int i) {
+                }
+            });
+            final AlertDialog alertDialog = builder.create();
+            alertDialog.show();
+//            SharedPreferences sharedPreferences =
+//                    PreferenceManager.getDefaultSharedPreferences(requireContext());
+//            sharedPreferences = null;
             return true;
         });
     }

From 5c46412faac23f27940cba860c8e8a1219375edd Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 09:19:04 +1100
Subject: [PATCH 029/141] Changed alert dialogue.

---
 .../java/org/schabi/newpipe/settings/DebugSettingsFragment.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 56c88f730..88addd73e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -98,7 +98,7 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
             final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
             builder.setMessage("Resetting all settings will discard "
                     + "all of your preferred settings and restarts the app. "
-                    + "Are you sure you want to do this?");
+                    + "Are you sure you want to proceed?");
             builder.setCancelable(true);
             builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                 @Override

From 23de9bf93ed6629afc8080ffa6a3f2467d0f76b1 Mon Sep 17 00:00:00 2001
From: Zhidong Piao <u7139999@anu.edu.au>
Date: Sat, 29 Oct 2022 09:38:00 +1100
Subject: [PATCH 030/141] clear shared preference xmls

---
 .../newpipe/settings/DebugSettingsFragment.java     | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 88addd73e..5c5f6a039 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -3,9 +3,11 @@ package org.schabi.newpipe.settings;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.os.Bundle;
 
 import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
 
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.error.ErrorInfo;
@@ -94,6 +96,7 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
 
         // Resets all settings by deleting shared preference and restarting the app
         // A dialogue will pop up to confirm if user intends to reset all settings
+        assert resetSettings != null;
         resetSettings.setOnPreferenceClickListener(preference -> {
             final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
             builder.setMessage("Resetting all settings will discard "
@@ -113,9 +116,13 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
             });
             final AlertDialog alertDialog = builder.create();
             alertDialog.show();
-//            SharedPreferences sharedPreferences =
-//                    PreferenceManager.getDefaultSharedPreferences(requireContext());
-//            sharedPreferences = null;
+
+            // delete all shared preferences xml files.
+            final SharedPreferences sharedPreferences =
+                    PreferenceManager.getDefaultSharedPreferences(requireContext());
+            sharedPreferences.edit().clear().apply();
+
+
             return true;
         });
     }

From 81ad50e82a0eb183cd348259445e8ffe25d9f6c8 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 09:52:22 +1100
Subject: [PATCH 031/141] Added delete xml method inside the yes dialogue.

---
 .../settings/DebugSettingsFragment.java       | 27 ++++++++-----------
 1 file changed, 11 insertions(+), 16 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 5c5f6a039..5952fbe5b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -1,7 +1,6 @@
 package org.schabi.newpipe.settings;
 
 import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
@@ -98,31 +97,27 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
         // A dialogue will pop up to confirm if user intends to reset all settings
         assert resetSettings != null;
         resetSettings.setOnPreferenceClickListener(preference -> {
+            // Show Alert Dialogue
             final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
             builder.setMessage("Resetting all settings will discard "
                     + "all of your preferred settings and restarts the app. "
                     + "Are you sure you want to proceed?");
             builder.setCancelable(true);
-            builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(final DialogInterface dialogInterface, final int i) {
-                    NavigationHelper.restartApp(getActivity());
+            builder.setPositiveButton("Yes", (dialogInterface, i) -> {
+                // Deletes all shared preferences xml files.
+                final SharedPreferences sharedPreferences =
+                        PreferenceManager.getDefaultSharedPreferences(requireContext());
+                sharedPreferences.edit().clear().apply();
+                // Restarts the app
+                if (getActivity() == null) {
+                    return;
                 }
+                NavigationHelper.restartApp(getActivity());
             });
-            builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(final DialogInterface dialogInterface, final int i) {
-                }
+            builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
             });
             final AlertDialog alertDialog = builder.create();
             alertDialog.show();
-
-            // delete all shared preferences xml files.
-            final SharedPreferences sharedPreferences =
-                    PreferenceManager.getDefaultSharedPreferences(requireContext());
-            sharedPreferences.edit().clear().apply();
-
-
             return true;
         });
     }

From 06d256294f31e944cc41ff9af12b13ce10b39202 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 10:24:11 +1100
Subject: [PATCH 032/141] Revert changes made to dev.

---
 gradle/wrapper/gradle-wrapper.properties |  2 +-
 settings.gradle                          | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index fcaba806a..28d3f9e02 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,3 @@
-#Fri Oct 21 13:32:17 AEDT 2022
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
@@ -7,3 +6,4 @@ networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/settings.gradle b/settings.gradle
index a0aed9527..0338fde6c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,8 +4,8 @@ include ':app'
 // We assume, that NewPipe and NewPipe Extractor have the same parent directory.
 // If this is not the case, please change the path in includeBuild().
 
-includeBuild('../NewPipeExtractor') {
-    dependencySubstitution {
-        substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
-    }
-}
+//includeBuild('../NewPipeExtractor') {
+//    dependencySubstitution {
+//        substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
+//    }
+//}

From a239a26b17c1259b1e261d3bcf0c99b1e1581059 Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 10:26:11 +1100
Subject: [PATCH 033/141] Revert changes made to dev.

---
 .../org/schabi/newpipe/settings/SettingsResourceRegistry.java    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
index 2fe4f42ed..b3d0741bb 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
@@ -30,6 +30,7 @@ public final class SettingsResourceRegistry {
 
     private SettingsResourceRegistry() {
         add(MainSettingsFragment.class, R.xml.main_settings).setSearchable(false);
+
         add(AppearanceSettingsFragment.class, R.xml.appearance_settings);
         add(ContentSettingsFragment.class, R.xml.content_settings);
         add(DebugSettingsFragment.class, R.xml.debug_settings).setSearchable(false);

From 25a73090f5bb36f77cb77edca11f89c99513f49d Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 10:29:16 +1100
Subject: [PATCH 034/141] Revert changes made to dev.

---
 app/src/main/res/values/settings_keys.xml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index ea283e62c..51abe14fb 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -223,9 +223,6 @@
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
 
-    <!-- RESET ONLY -->
-    <string name="reset_settings">reset_settings</string>
-
     <!-- DEBUG ONLY -->
     <string name="debug_pref_screen_key">debug_pref_screen_key</string>
     <string name="allow_heap_dumping_key">allow_heap_dumping_key</string>

From 40a3e1b18a4c2abfd412c875d83912ea6d710def Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 10:30:41 +1100
Subject: [PATCH 035/141] Revert committed file change of settings key.

---
 app/src/main/res/values/settings_keys.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 51abe14fb..ea283e62c 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -223,6 +223,9 @@
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
 
+    <!-- RESET ONLY -->
+    <string name="reset_settings">reset_settings</string>
+
     <!-- DEBUG ONLY -->
     <string name="debug_pref_screen_key">debug_pref_screen_key</string>
     <string name="allow_heap_dumping_key">allow_heap_dumping_key</string>

From d7ef9b1f0c1cbba8cf216ab696a0ba93a281c24b Mon Sep 17 00:00:00 2001
From: Vincent Tanumihardja <u7145408@anu.edu.au>
Date: Sat, 29 Oct 2022 11:33:10 +1100
Subject: [PATCH 036/141] Added strings to strings.xml to allow translation.

---
 .../schabi/newpipe/settings/DebugSettingsFragment.java    | 8 +++-----
 app/src/main/res/values/strings.xml                       | 2 ++
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 5952fbe5b..f2a4e1190 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -99,11 +99,9 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
         resetSettings.setOnPreferenceClickListener(preference -> {
             // Show Alert Dialogue
             final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-            builder.setMessage("Resetting all settings will discard "
-                    + "all of your preferred settings and restarts the app. "
-                    + "Are you sure you want to proceed?");
+            builder.setMessage(R.string.reset_all_settings);
             builder.setCancelable(true);
-            builder.setPositiveButton("Yes", (dialogInterface, i) -> {
+            builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
                 // Deletes all shared preferences xml files.
                 final SharedPreferences sharedPreferences =
                         PreferenceManager.getDefaultSharedPreferences(requireContext());
@@ -114,7 +112,7 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
                 }
                 NavigationHelper.restartApp(getActivity());
             });
-            builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
+            builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
             });
             final AlertDialog alertDialog = builder.create();
             alertDialog.show();
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f83938139..9c843784e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -595,6 +595,8 @@
         <item quantity="one">Download finished</item>
         <item quantity="other">%s downloads finished</item>
     </plurals>
+    <!-- dialog about resetting settings -->
+    <string name="reset_all_settings">Resetting all settings will discard all of your preferred settings and restarts the app. Are you sure you want to proceed?</string>
     <!-- dialog about existing downloads -->
     <string name="generate_unique_name">Generate unique name</string>
     <string name="overwrite">Overwrite</string>

From ad0855ac83715179d1be3068492f136271e78072 Mon Sep 17 00:00:00 2001
From: vincetzr <110076924+vincetzr@users.noreply.github.com>
Date: Sat, 29 Oct 2022 21:21:26 +1100
Subject: [PATCH 037/141] Update app/src/main/res/xml/debug_settings.xml

Ensuring title to be fully displayed on small devices.

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
---
 app/src/main/res/xml/debug_settings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 511bcd19a..1c66dbdb3 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -74,5 +74,6 @@
     <Preference
         android:key="@string/reset_settings"
         android:title="@string/settings_category_reset_title"
+        app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
 </PreferenceScreen>

From f2e352832a12d1282a4b6b7dddf389cf8e7887cf Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Sun, 17 Sep 2023 17:21:59 +0200
Subject: [PATCH 038/141] Create new settings category: Backup and restore

Following settings have been move to the new category:
- import database (from ContenttSettings)
- export database (from ContenttSettings)
- reset settings (from DebugSettings)
---
 .../BackupRestoreSettingsFragment.java        | 271 ++++++++++++++++++
 .../settings/ContentSettingsFragment.java     | 217 --------------
 .../settings/DebugSettingsFragment.java       |  32 ---
 .../newpipe/settings/NewPipeSettings.java     |   1 +
 .../settings/SettingsResourceRegistry.java    |   1 +
 app/src/main/res/values/settings_keys.xml     |   1 -
 app/src/main/res/values/strings.xml           |   1 +
 .../main/res/xml/backup_restore_settings.xml  |  24 ++
 app/src/main/res/xml/content_settings.xml     |  14 -
 app/src/main/res/xml/debug_settings.xml       |   5 -
 app/src/main/res/xml/main_settings.xml        |   6 +
 11 files changed, 304 insertions(+), 269 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
 create mode 100644 app/src/main/res/xml/backup_restore_settings.xml

diff --git a/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
new file mode 100644
index 000000000..bc24fbe81
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
@@ -0,0 +1,271 @@
+package org.schabi.newpipe.settings;
+
+import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+
+import org.schabi.newpipe.NewPipeDatabase;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.error.ErrorUtil;
+import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
+import org.schabi.newpipe.streams.io.StoredFileHelper;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.ZipHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+
+public class BackupRestoreSettingsFragment extends BasePreferenceFragment {
+
+    private static final String ZIP_MIME_TYPE = "application/zip";
+
+    private final SimpleDateFormat exportDateFormat =
+            new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+    private ContentSettingsManager manager;
+    private String importExportDataPathKey;
+    private final ActivityResultLauncher<Intent> requestImportPathLauncher =
+            registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+                    this::requestImportPathResult);
+    private final ActivityResultLauncher<Intent> requestExportPathLauncher =
+            registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+                    this::requestExportPathResult);
+
+
+    @Override
+    public void onCreatePreferences(@Nullable final Bundle savedInstanceState,
+                                    @Nullable final String rootKey) {
+        final File homeDir = ContextCompat.getDataDir(requireContext());
+        Objects.requireNonNull(homeDir);
+        manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
+        manager.deleteSettingsFile();
+
+        importExportDataPathKey = getString(R.string.import_export_data_path);
+
+
+        addPreferencesFromResourceRegistry();
+
+        final Preference importDataPreference = requirePreference(R.string.import_data);
+        importDataPreference.setOnPreferenceClickListener((Preference p) -> {
+            NoFileManagerSafeGuard.launchSafe(
+                    requestImportPathLauncher,
+                    StoredFileHelper.getPicker(requireContext(),
+                            ZIP_MIME_TYPE, getImportExportDataUri()),
+                    TAG,
+                    getContext()
+            );
+
+            return true;
+        });
+
+        final Preference exportDataPreference = requirePreference(R.string.export_data);
+        exportDataPreference.setOnPreferenceClickListener((final Preference p) -> {
+            NoFileManagerSafeGuard.launchSafe(
+                    requestExportPathLauncher,
+                    StoredFileHelper.getNewPicker(requireContext(),
+                            "NewPipeData-" + exportDateFormat.format(new Date()) + ".zip",
+                            ZIP_MIME_TYPE, getImportExportDataUri()),
+                    TAG,
+                    getContext()
+            );
+
+            return true;
+        });
+
+        final Preference resetSettings = findPreference(getString(R.string.reset_settings));
+        // Resets all settings by deleting shared preference and restarting the app
+        // A dialogue will pop up to confirm if user intends to reset all settings
+        assert resetSettings != null;
+        resetSettings.setOnPreferenceClickListener(preference -> {
+            // Show Alert Dialogue
+            final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            builder.setMessage(R.string.reset_all_settings);
+            builder.setCancelable(true);
+            builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
+                // Deletes all shared preferences xml files.
+                final SharedPreferences sharedPreferences =
+                        PreferenceManager.getDefaultSharedPreferences(requireContext());
+                sharedPreferences.edit().clear().apply();
+                // Restarts the app
+                if (getActivity() == null) {
+                    return;
+                }
+                NavigationHelper.restartApp(getActivity());
+            });
+            builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
+            });
+            final AlertDialog alertDialog = builder.create();
+            alertDialog.show();
+            return true;
+        });
+    }
+
+    private void requestExportPathResult(final ActivityResult result) {
+        assureCorrectAppLanguage(requireContext());
+        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+            // will be saved only on success
+            final Uri lastExportDataUri = result.getData().getData();
+
+            final StoredFileHelper file = new StoredFileHelper(
+                    requireContext(), result.getData().getData(), ZIP_MIME_TYPE);
+
+            exportDatabase(file, lastExportDataUri);
+        }
+    }
+
+    private void requestImportPathResult(final ActivityResult result) {
+        assureCorrectAppLanguage(requireContext());
+        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+            // will be saved only on success
+            final Uri lastImportDataUri = result.getData().getData();
+
+            final StoredFileHelper file = new StoredFileHelper(
+                    requireContext(), result.getData().getData(), ZIP_MIME_TYPE);
+
+            new androidx.appcompat.app.AlertDialog.Builder(requireActivity())
+                    .setMessage(R.string.override_current_data)
+                    .setPositiveButton(R.string.ok, (d, id) ->
+                            importDatabase(file, lastImportDataUri))
+                    .setNegativeButton(R.string.cancel, (d, id) ->
+                            d.cancel())
+                    .show();
+        }
+    }
+
+    private void exportDatabase(final StoredFileHelper file, final Uri exportDataUri) {
+        try {
+            //checkpoint before export
+            NewPipeDatabase.checkpoint();
+
+            final SharedPreferences preferences = PreferenceManager
+                    .getDefaultSharedPreferences(requireContext());
+            manager.exportDatabase(preferences, file);
+
+            saveLastImportExportDataUri(exportDataUri); // save export path only on success
+            Toast.makeText(requireContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT)
+                    .show();
+        } catch (final Exception e) {
+            ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e);
+        }
+    }
+
+    private void importDatabase(final StoredFileHelper file, final Uri importDataUri) {
+        // check if file is supported
+        if (!ZipHelper.isValidZipFile(file)) {
+            Toast.makeText(requireContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
+                    .show();
+            return;
+        }
+
+        try {
+            if (!manager.ensureDbDirectoryExists()) {
+                throw new IOException("Could not create databases dir");
+            }
+
+            if (!manager.extractDb(file)) {
+                Toast.makeText(requireContext(), R.string.could_not_import_all_files,
+                                Toast.LENGTH_LONG)
+                        .show();
+            }
+
+            // if settings file exist, ask if it should be imported.
+            if (manager.extractSettings(file)) {
+                new androidx.appcompat.app.AlertDialog.Builder(requireContext())
+                        .setTitle(R.string.import_settings)
+                        .setNegativeButton(R.string.cancel, (dialog, which) -> {
+                            dialog.dismiss();
+                            finishImport(importDataUri);
+                        })
+                        .setPositiveButton(R.string.ok, (dialog, which) -> {
+                            dialog.dismiss();
+                            final Context context = requireContext();
+                            final SharedPreferences prefs = PreferenceManager
+                                    .getDefaultSharedPreferences(context);
+                            manager.loadSharedPreferences(prefs);
+                            cleanImport(context, prefs);
+                            finishImport(importDataUri);
+                        })
+                        .show();
+            } else {
+                finishImport(importDataUri);
+            }
+        } catch (final Exception e) {
+            ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
+        }
+    }
+
+    /**
+     * Remove settings that are not supposed to be imported on different devices
+     * and reset them to default values.
+     * @param context the context used for the import
+     * @param prefs the preferences used while running the import
+     */
+    private void cleanImport(@NonNull final Context context,
+                             @NonNull final SharedPreferences prefs) {
+        // Check if media tunnelling needs to be disabled automatically,
+        // if it was disabled automatically in the imported preferences.
+        final String tunnelingKey = context.getString(R.string.disable_media_tunneling_key);
+        final String automaticTunnelingKey =
+                context.getString(R.string.disabled_media_tunneling_automatically_key);
+        // R.string.disable_media_tunneling_key should always be true
+        // if R.string.disabled_media_tunneling_automatically_key equals 1,
+        // but we double check here just to be sure and to avoid regressions
+        // caused by possible later modification of the media tunneling functionality.
+        // R.string.disabled_media_tunneling_automatically_key == 0:
+        //     automatic value overridden by user in settings
+        // R.string.disabled_media_tunneling_automatically_key == -1: not set
+        final boolean wasMediaTunnelingDisabledAutomatically =
+                prefs.getInt(automaticTunnelingKey, -1) == 1
+                        && prefs.getBoolean(tunnelingKey, false);
+        if (wasMediaTunnelingDisabledAutomatically) {
+            prefs.edit()
+                    .putInt(automaticTunnelingKey, -1)
+                    .putBoolean(tunnelingKey, false)
+                    .apply();
+            NewPipeSettings.setMediaTunneling(context);
+        }
+    }
+
+    /**
+     * Save import path and restart system.
+     *
+     * @param importDataUri The import path to save
+     */
+    private void finishImport(final Uri importDataUri) {
+        // save import path only on success
+        saveLastImportExportDataUri(importDataUri);
+        // restart app to properly load db
+        NavigationHelper.restartApp(requireActivity());
+    }
+
+    private Uri getImportExportDataUri() {
+        final String path = defaultPreferences.getString(importExportDataPathKey, null);
+        return isBlank(path) ? null : Uri.parse(path);
+    }
+
+    private void saveLastImportExportDataUri(final Uri importExportDataUri) {
+        final SharedPreferences.Editor editor = defaultPreferences.edit()
+                .putString(importExportDataPathKey, importExportDataUri.toString());
+        editor.apply();
+    }
+}
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 ee34f01dd..ec3b1b2d7 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -1,104 +1,34 @@
 package org.schabi.newpipe.settings;
 
-import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-
-import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.widget.Toast;
 
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
 import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
 
 import org.schabi.newpipe.DownloaderImpl;
-import org.schabi.newpipe.NewPipeDatabase;
 import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.localization.ContentCountry;
 import org.schabi.newpipe.extractor.localization.Localization;
-import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard;
-import org.schabi.newpipe.streams.io.StoredFileHelper;
-import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PicassoHelper;
-import org.schabi.newpipe.util.ZipHelper;
 
-import java.io.File;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
 
 public class ContentSettingsFragment extends BasePreferenceFragment {
-    private static final String ZIP_MIME_TYPE = "application/zip";
-
-    private final SimpleDateFormat exportDateFormat =
-            new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
-
-    private ContentSettingsManager manager;
-
-    private String importExportDataPathKey;
     private String youtubeRestrictedModeEnabledKey;
 
     private Localization initialSelectedLocalization;
     private ContentCountry initialSelectedContentCountry;
     private String initialLanguage;
-    private final ActivityResultLauncher<Intent> requestImportPathLauncher =
-            registerForActivityResult(new StartActivityForResult(), this::requestImportPathResult);
-    private final ActivityResultLauncher<Intent> requestExportPathLauncher =
-            registerForActivityResult(new StartActivityForResult(), this::requestExportPathResult);
 
     @Override
     public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
-        final File homeDir = ContextCompat.getDataDir(requireContext());
-        Objects.requireNonNull(homeDir);
-        manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
-        manager.deleteSettingsFile();
-
-        importExportDataPathKey = getString(R.string.import_export_data_path);
         youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
 
         addPreferencesFromResourceRegistry();
 
-        final Preference importDataPreference = requirePreference(R.string.import_data);
-        importDataPreference.setOnPreferenceClickListener((Preference p) -> {
-            NoFileManagerSafeGuard.launchSafe(
-                    requestImportPathLauncher,
-                    StoredFileHelper.getPicker(requireContext(),
-                            ZIP_MIME_TYPE, getImportExportDataUri()),
-                    TAG,
-                    getContext()
-            );
-
-            return true;
-        });
-
-        final Preference exportDataPreference = requirePreference(R.string.export_data);
-        exportDataPreference.setOnPreferenceClickListener((final Preference p) -> {
-            NoFileManagerSafeGuard.launchSafe(
-                    requestExportPathLauncher,
-                    StoredFileHelper.getNewPicker(requireContext(),
-                            "NewPipeData-" + exportDateFormat.format(new Date()) + ".zip",
-                            ZIP_MIME_TYPE, getImportExportDataUri()),
-                    TAG,
-                    getContext()
-            );
-
-            return true;
-        });
-
         initialSelectedLocalization = org.schabi.newpipe.util.Localization
                 .getPreferredLocalization(requireContext());
         initialSelectedContentCountry = org.schabi.newpipe.util.Localization
@@ -154,151 +84,4 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
             NewPipe.setupLocalization(selectedLocalization, selectedContentCountry);
         }
     }
-
-    private void requestExportPathResult(final ActivityResult result) {
-        assureCorrectAppLanguage(getContext());
-        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
-            // will be saved only on success
-            final Uri lastExportDataUri = result.getData().getData();
-
-            final StoredFileHelper file =
-                    new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE);
-
-            exportDatabase(file, lastExportDataUri);
-        }
-    }
-
-    private void requestImportPathResult(final ActivityResult result) {
-        assureCorrectAppLanguage(getContext());
-        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
-            // will be saved only on success
-            final Uri lastImportDataUri = result.getData().getData();
-
-            final StoredFileHelper file =
-                    new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE);
-
-            new AlertDialog.Builder(requireActivity())
-                    .setMessage(R.string.override_current_data)
-                    .setPositiveButton(R.string.ok, (d, id) ->
-                            importDatabase(file, lastImportDataUri))
-                    .setNegativeButton(R.string.cancel, (d, id) ->
-                            d.cancel())
-                    .show();
-        }
-    }
-
-    private void exportDatabase(final StoredFileHelper file, final Uri exportDataUri) {
-        try {
-            //checkpoint before export
-            NewPipeDatabase.checkpoint();
-
-            final SharedPreferences preferences = PreferenceManager
-                    .getDefaultSharedPreferences(requireContext());
-            manager.exportDatabase(preferences, file);
-
-            saveLastImportExportDataUri(exportDataUri); // save export path only on success
-            Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
-        } catch (final Exception e) {
-            ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e);
-        }
-    }
-
-    private void importDatabase(final StoredFileHelper file, final Uri importDataUri) {
-        // check if file is supported
-        if (!ZipHelper.isValidZipFile(file)) {
-            Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
-                    .show();
-            return;
-        }
-
-        try {
-            if (!manager.ensureDbDirectoryExists()) {
-                throw new IOException("Could not create databases dir");
-            }
-
-            if (!manager.extractDb(file)) {
-                Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG)
-                    .show();
-            }
-
-            // if settings file exist, ask if it should be imported.
-            if (manager.extractSettings(file)) {
-                new AlertDialog.Builder(requireContext())
-                        .setTitle(R.string.import_settings)
-                        .setNegativeButton(R.string.cancel, (dialog, which) -> {
-                            dialog.dismiss();
-                            finishImport(importDataUri);
-                        })
-                        .setPositiveButton(R.string.ok, (dialog, which) -> {
-                            dialog.dismiss();
-                            final Context context = requireContext();
-                            final SharedPreferences prefs = PreferenceManager
-                                    .getDefaultSharedPreferences(context);
-                            manager.loadSharedPreferences(prefs);
-                            cleanImport(context, prefs);
-                            finishImport(importDataUri);
-                        })
-                        .show();
-            } else {
-                finishImport(importDataUri);
-            }
-        } catch (final Exception e) {
-            ErrorUtil.showUiErrorSnackbar(this, "Importing database", e);
-        }
-    }
-
-    /**
-     * Remove settings that are not supposed to be imported on different devices
-     * and reset them to default values.
-     * @param context the context used for the import
-     * @param prefs the preferences used while running the import
-     */
-    private void cleanImport(@NonNull final Context context,
-                             @NonNull final SharedPreferences prefs) {
-        // Check if media tunnelling needs to be disabled automatically,
-        // if it was disabled automatically in the imported preferences.
-        final String tunnelingKey = context.getString(R.string.disable_media_tunneling_key);
-        final String automaticTunnelingKey =
-                context.getString(R.string.disabled_media_tunneling_automatically_key);
-        // R.string.disable_media_tunneling_key should always be true
-        // if R.string.disabled_media_tunneling_automatically_key equals 1,
-        // but we double check here just to be sure and to avoid regressions
-        // caused by possible later modification of the media tunneling functionality.
-        // R.string.disabled_media_tunneling_automatically_key == 0:
-        //     automatic value overridden by user in settings
-        // R.string.disabled_media_tunneling_automatically_key == -1: not set
-        final boolean wasMediaTunnelingDisabledAutomatically =
-                prefs.getInt(automaticTunnelingKey, -1) == 1
-                        && prefs.getBoolean(tunnelingKey, false);
-        if (wasMediaTunnelingDisabledAutomatically) {
-            prefs.edit()
-                    .putInt(automaticTunnelingKey, -1)
-                    .putBoolean(tunnelingKey, false)
-                    .apply();
-            NewPipeSettings.setMediaTunneling(context);
-        }
-    }
-
-    /**
-     * Save import path and restart system.
-     *
-     * @param importDataUri The import path to save
-     */
-    private void finishImport(final Uri importDataUri) {
-        // save import path only on success
-        saveLastImportExportDataUri(importDataUri);
-        // restart app to properly load db
-        NavigationHelper.restartApp(requireActivity());
-    }
-
-    private Uri getImportExportDataUri() {
-        final String path = defaultPreferences.getString(importExportDataPathKey, null);
-        return isBlank(path) ? null : Uri.parse(path);
-    }
-
-    private void saveLastImportExportDataUri(final Uri importExportDataUri) {
-        final SharedPreferences.Editor editor = defaultPreferences.edit()
-                .putString(importExportDataPathKey, importExportDataUri.toString());
-        editor.apply();
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index f2a4e1190..0f4c9765e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -1,19 +1,15 @@
 package org.schabi.newpipe.settings;
 
-import android.app.AlertDialog;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.os.Bundle;
 
 import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
 
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
-import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PicassoHelper;
 
 import java.util.Optional;
@@ -39,8 +35,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
                 findPreference(getString(R.string.show_error_snackbar_key));
         final Preference createErrorNotificationPreference =
                 findPreference(getString(R.string.create_error_notification_key));
-        final Preference resetSettings =
-                findPreference(getString(R.string.reset_settings));
 
         assert allowHeapDumpingPreference != null;
         assert showMemoryLeaksPreference != null;
@@ -92,32 +86,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
                     new ErrorInfo(new RuntimeException(DUMMY), UserAction.UI_ERROR, DUMMY));
             return true;
         });
-
-        // Resets all settings by deleting shared preference and restarting the app
-        // A dialogue will pop up to confirm if user intends to reset all settings
-        assert resetSettings != null;
-        resetSettings.setOnPreferenceClickListener(preference -> {
-            // Show Alert Dialogue
-            final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-            builder.setMessage(R.string.reset_all_settings);
-            builder.setCancelable(true);
-            builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
-                // Deletes all shared preferences xml files.
-                final SharedPreferences sharedPreferences =
-                        PreferenceManager.getDefaultSharedPreferences(requireContext());
-                sharedPreferences.edit().clear().apply();
-                // Restarts the app
-                if (getActivity() == null) {
-                    return;
-                }
-                NavigationHelper.restartApp(getActivity());
-            });
-            builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
-            });
-            final AlertDialog alertDialog = builder.create();
-            alertDialog.show();
-            return true;
-        });
     }
 
     /**
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
index b85b95eb0..f280324cf 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
@@ -63,6 +63,7 @@ public final class NewPipeSettings {
         PreferenceManager.setDefaultValues(context, R.xml.player_notification_settings, true);
         PreferenceManager.setDefaultValues(context, R.xml.update_settings, true);
         PreferenceManager.setDefaultValues(context, R.xml.debug_settings, true);
+        PreferenceManager.setDefaultValues(context, R.xml.backup_restore_settings, true);
 
         saveDefaultVideoDownloadDirectory(context);
         saveDefaultAudioDownloadDirectory(context);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
index b3d0741bb..06e0a7c1e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
@@ -41,6 +41,7 @@ public final class SettingsResourceRegistry {
         add(UpdateSettingsFragment.class, R.xml.update_settings);
         add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
         add(ExoPlayerSettingsFragment.class, R.xml.exoplayer_settings);
+        add(BackupRestoreSettingsFragment.class, R.xml.backup_restore_settings);
     }
 
     private SettingRegistryEntry add(
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index ea283e62c..30efca080 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -223,7 +223,6 @@
     <string name="prefer_descriptive_audio_key">prefer_descriptive_audio</string>
     <string name="last_resize_mode">last_resize_mode</string>
 
-    <!-- RESET ONLY -->
     <string name="reset_settings">reset_settings</string>
 
     <!-- DEBUG ONLY -->
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9c843784e..314eaa9bd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -155,6 +155,7 @@
     <string name="settings_category_updates_title">Updates</string>
     <string name="settings_category_player_notification_title">Player notification</string>
     <string name="settings_category_player_notification_summary">Configure current playing stream notification</string>
+    <string name="settings_category_backup_restore_title">Backup and restore</string>
     <string name="background_player_playing_toast">Playing in background</string>
     <string name="popup_playing_toast">Playing in popup mode</string>
     <string name="content">Content</string>
diff --git a/app/src/main/res/xml/backup_restore_settings.xml b/app/src/main/res/xml/backup_restore_settings.xml
new file mode 100644
index 000000000..b8fee0aa6
--- /dev/null
+++ b/app/src/main/res/xml/backup_restore_settings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:title="@string/settings_category_backup_restore_title">
+    <Preference
+        android:key="@string/import_data"
+        android:summary="@string/import_data_summary"
+        android:title="@string/import_data_title"
+        app:singleLineTitle="false"
+        app:iconSpaceReserved="false" />
+
+    <Preference
+        android:key="@string/export_data"
+        android:summary="@string/export_data_summary"
+        android:title="@string/export_data_title"
+        app:singleLineTitle="false"
+        app:iconSpaceReserved="false" />
+
+    <Preference
+        android:key="@string/reset_settings"
+        android:title="@string/settings_category_reset_title"
+        app:singleLineTitle="false"
+        app:iconSpaceReserved="false" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index 73a849af7..dac5dccc1 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -124,20 +124,6 @@
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
 
-    <Preference
-        android:key="@string/import_data"
-        android:summary="@string/import_data_summary"
-        android:title="@string/import_data_title"
-        app:singleLineTitle="false"
-        app:iconSpaceReserved="false" />
-
-    <Preference
-        android:key="@string/export_data"
-        android:summary="@string/export_data_summary"
-        android:title="@string/export_data_title"
-        app:singleLineTitle="false"
-        app:iconSpaceReserved="false" />
-
     <PreferenceCategory
         android:layout="@layout/settings_category_header_layout"
         android:title="@string/settings_category_feed_title"
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 1c66dbdb3..84bb281f3 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -71,9 +71,4 @@
         android:title="@string/create_error_notification"
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
-    <Preference
-        android:key="@string/reset_settings"
-        android:title="@string/settings_category_reset_title"
-        app:singleLineTitle="false"
-        app:iconSpaceReserved="false" />
 </PreferenceScreen>
diff --git a/app/src/main/res/xml/main_settings.xml b/app/src/main/res/xml/main_settings.xml
index 1b1c17e85..5f96989f9 100644
--- a/app/src/main/res/xml/main_settings.xml
+++ b/app/src/main/res/xml/main_settings.xml
@@ -47,6 +47,12 @@
         android:title="@string/settings_category_updates_title"
         app:iconSpaceReserved="false" />
 
+    <PreferenceScreen
+        android:fragment="org.schabi.newpipe.settings.BackupRestoreSettingsFragment"
+        android:icon="@drawable/ic_settings_backup_restore"
+        android:title="@string/settings_category_backup_restore_title"
+        app:iconSpaceReserved="false" />
+
     <PreferenceScreen
         android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
         android:icon="@drawable/ic_bug_report"

From ff1aca272e6e0b0b09778f1efec9327326a9d0ea Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Sun, 17 Sep 2023 17:31:45 +0200
Subject: [PATCH 039/141] Remove strange change

---
 gradle/wrapper/gradle-wrapper.properties | 2 --
 1 file changed, 2 deletions(-)

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 28d3f9e02..2c3425d49 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -5,5 +5,3 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists

From df2e0be08dd36c89b747a31dc3ab40d016f9d1e6 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Sun, 17 Sep 2023 17:35:52 +0200
Subject: [PATCH 040/141] Add summary to reset preference

---
 app/src/main/res/values/strings.xml              | 5 +++--
 app/src/main/res/xml/backup_restore_settings.xml | 3 ++-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 314eaa9bd..1dbffa4ae 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -151,7 +151,6 @@
     <string name="settings_category_history_title">History and cache</string>
     <string name="settings_category_appearance_title">Appearance</string>
     <string name="settings_category_debug_title">Debug</string>
-    <string name="settings_category_reset_title">Reset All Settings</string>
     <string name="settings_category_updates_title">Updates</string>
     <string name="settings_category_player_notification_title">Player notification</string>
     <string name="settings_category_player_notification_summary">Configure current playing stream notification</string>
@@ -596,8 +595,10 @@
         <item quantity="one">Download finished</item>
         <item quantity="other">%s downloads finished</item>
     </plurals>
+    <string name="reset_settings_title">Reset settings</string>
+    <string name="reset_settings_summary">Reset all settings to their default values</string>
     <!-- dialog about resetting settings -->
-    <string name="reset_all_settings">Resetting all settings will discard all of your preferred settings and restarts the app. Are you sure you want to proceed?</string>
+    <string name="reset_all_settings">Resetting all settings will discard all of your preferred settings and restarts the app.\n\nAre you sure you want to proceed?</string>
     <!-- dialog about existing downloads -->
     <string name="generate_unique_name">Generate unique name</string>
     <string name="overwrite">Overwrite</string>
diff --git a/app/src/main/res/xml/backup_restore_settings.xml b/app/src/main/res/xml/backup_restore_settings.xml
index b8fee0aa6..ef6a3cde3 100644
--- a/app/src/main/res/xml/backup_restore_settings.xml
+++ b/app/src/main/res/xml/backup_restore_settings.xml
@@ -18,7 +18,8 @@
 
     <Preference
         android:key="@string/reset_settings"
-        android:title="@string/settings_category_reset_title"
+        android:title="@string/reset_settings_title"
+        android:summary="@string/reset_settings_summary"
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
 </PreferenceScreen>
\ No newline at end of file

From 8f4cd032b76412f8f05ee2800021162fc89e9795 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 10:47:40 +0200
Subject: [PATCH 041/141] Remove mini variant and move upload date to top in
 comments

---
 .../newpipe/info_list/InfoItemBuilder.java    |  6 +-
 .../newpipe/info_list/InfoListAdapter.java    | 12 ++--
 ...Holder.java => CommentInfoItemHolder.java} | 45 ++++++------
 .../holder/CommentsInfoItemHolder.java        | 63 -----------------
 app/src/main/res/layout/fragment_comments.xml |  2 +-
 ...omments_item.xml => list_comment_item.xml} | 14 +---
 .../res/layout/list_comments_mini_item.xml    | 69 -------------------
 7 files changed, 31 insertions(+), 180 deletions(-)
 rename app/src/main/java/org/schabi/newpipe/info_list/holder/{CommentsMiniInfoItemHolder.java => CommentInfoItemHolder.java} (90%)
 delete mode 100644 app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
 rename app/src/main/res/layout/{list_comments_item.xml => list_comment_item.xml} (89%)
 delete mode 100644 app/src/main/res/layout/list_comments_mini_item.xml

diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
index 68f19ee97..d959c6327 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -13,8 +13,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.InfoItemHolder;
 import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
@@ -87,8 +86,7 @@ public class InfoItemBuilder {
                 return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
                         : new PlaylistInfoItemHolder(this, parent);
             case COMMENT:
-                return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent)
-                        : new CommentsInfoItemHolder(this, parent);
+                return new CommentInfoItemHolder(this, parent);
             default:
                 throw new RuntimeException("InfoType not expected = " + infoType.name());
         }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index a13f0e5aa..575568c00 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -21,8 +21,7 @@ import org.schabi.newpipe.info_list.holder.ChannelCardInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.InfoItemHolder;
 import org.schabi.newpipe.info_list.holder.PlaylistCardInfoItemHolder;
 import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
@@ -79,8 +78,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
     private static final int PLAYLIST_HOLDER_TYPE = 0x301;
     private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
     private static final int CARD_PLAYLIST_HOLDER_TYPE = 0x303;
-    private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
-    private static final int COMMENT_HOLDER_TYPE = 0x401;
+    private static final int COMMENT_HOLDER_TYPE = 0x400;
 
     private final LayoutInflater layoutInflater;
     private final InfoItemBuilder infoItemBuilder;
@@ -271,7 +269,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
                     return PLAYLIST_HOLDER_TYPE;
                 }
             case COMMENT:
-                return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
+                return COMMENT_HOLDER_TYPE;
             default:
                 return -1;
         }
@@ -320,10 +318,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
                 return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
             case CARD_PLAYLIST_HOLDER_TYPE:
                 return new PlaylistCardInfoItemHolder(infoItemBuilder, parent);
-            case MINI_COMMENT_HOLDER_TYPE:
-                return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
             case COMMENT_HOLDER_TYPE:
-                return new CommentsInfoItemHolder(infoItemBuilder, parent);
+                return new CommentInfoItemHolder(infoItemBuilder, parent);
             default:
                 return new FallbackViewHolder(new View(parent.getContext()));
         }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
similarity index 90%
rename from app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
rename to app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index d6a08e6cb..afe641c18 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -41,8 +41,8 @@ import java.util.function.Consumer;
 
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
-public class CommentsMiniInfoItemHolder extends InfoItemHolder {
-    private static final String TAG = "CommentsMiniIIHolder";
+public class CommentInfoItemHolder extends InfoItemHolder {
+    private static final String TAG = "CommentIIHolder";
     private static final String ELLIPSIS = "…";
 
     private static final int COMMENT_DEFAULT_LINES = 2;
@@ -58,22 +58,26 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
     private final ImageView itemThumbnailView;
     private final TextView itemContentView;
     private final TextView itemLikesCountView;
-    private final TextView itemPublishedTime;
+    private final TextView itemTitleView;
+    private final ImageView itemHeartView;
+    private final ImageView itemPinnedView;
 
     private final CompositeDisposable disposables = new CompositeDisposable();
     @Nullable private Description commentText;
     @Nullable private StreamingService streamService;
     @Nullable private String streamUrl;
 
-    CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
-                               final ViewGroup parent) {
-        super(infoItemBuilder, layoutId, parent);
+    public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,
+                                 final ViewGroup parent) {
+        super(infoItemBuilder, R.layout.list_comment_item, parent);
 
         itemRoot = itemView.findViewById(R.id.itemRoot);
         itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
-        itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
-        itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
         itemContentView = itemView.findViewById(R.id.itemCommentContentView);
+        itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
+        itemTitleView = itemView.findViewById(R.id.itemTitleView);
+        itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
+        itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
 
         commentHorizontalPadding = (int) infoItemBuilder.getContext()
                 .getResources().getDimension(R.dimen.comments_horizontal_padding);
@@ -85,11 +89,6 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
         ellipsisWidthPx = paintAtContentSize.measureText(ELLIPSIS);
     }
 
-    public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
-                                      final ViewGroup parent) {
-        this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
-    }
-
     @Override
     public void updateFromItem(final InfoItem infoItem,
                                final HistoryRecordManager historyRecordManager) {
@@ -108,10 +107,19 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
             itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
                     commentHorizontalPadding, commentVerticalPadding);
         }
-
-
         itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
 
+        final String uploadDate;
+        if (item.getUploadDate() != null) {
+            uploadDate = Localization.relativeTime(item.getUploadDate().offsetDateTime());
+        } else {
+            uploadDate = item.getTextualUploadDate();
+        }
+        itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(), uploadDate));
+
+        itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
+        itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
+
         try {
             streamService = NewPipe.getService(item.getServiceId());
         } catch (final ExtractionException e) {
@@ -136,12 +144,6 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
             itemLikesCountView.setText("-");
         }
 
-        if (item.getUploadDate() != null) {
-            itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
-                    .offsetDateTime()));
-        } else {
-            itemPublishedTime.setText(item.getTextualUploadDate());
-        }
 
         itemView.setOnClickListener(view -> {
             toggleEllipsize();
@@ -150,7 +152,6 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
             }
         });
 
-
         itemView.setOnLongClickListener(view -> {
             if (DeviceUtils.isTv(itemBuilder.getContext())) {
                 openCommentAuthor(item);
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
deleted file mode 100644
index 4fc2d9f84..000000000
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.schabi.newpipe.info_list.holder;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
-import org.schabi.newpipe.info_list.InfoItemBuilder;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
-
-/*
- * Created by Christian Schabesberger on 12.02.17.
- *
- * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
- * ChannelInfoItemHolder .java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
-    public final TextView itemTitleView;
-    private final ImageView itemHeartView;
-    private final ImageView itemPinnedView;
-
-    public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
-        super(infoItemBuilder, R.layout.list_comments_item, parent);
-
-        itemTitleView = itemView.findViewById(R.id.itemTitleView);
-        itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
-        itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
-    }
-
-    @Override
-    public void updateFromItem(final InfoItem infoItem,
-                               final HistoryRecordManager historyRecordManager) {
-        super.updateFromItem(infoItem, historyRecordManager);
-
-        if (!(infoItem instanceof CommentsInfoItem)) {
-            return;
-        }
-        final CommentsInfoItem item = (CommentsInfoItem) infoItem;
-
-        itemTitleView.setText(item.getUploaderName());
-
-        itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
-
-        itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
-    }
-}
diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml
index b1b644d8c..2a8c747cd 100644
--- a/app/src/main/res/layout/fragment_comments.xml
+++ b/app/src/main/res/layout/fragment_comments.xml
@@ -9,7 +9,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:scrollbars="vertical"
-        tools:listitem="@layout/list_comments_item" />
+        tools:listitem="@layout/list_comment_item" />
 
     <ProgressBar
         android:id="@+id/loading_progress_bar"
diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comment_item.xml
similarity index 89%
rename from app/src/main/res/layout/list_comments_item.xml
rename to app/src/main/res/layout/list_comment_item.xml
index ad73c5ff4..26fcc9727 100644
--- a/app/src/main/res/layout/list_comments_item.xml
+++ b/app/src/main/res/layout/list_comment_item.xml
@@ -47,7 +47,7 @@
         android:lines="1"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textSize="@dimen/comment_item_title_text_size"
-        tools:text="Author Name, Lorem ipsum" />
+        tools:text="Author Name, Lorem ipsum · 5 months ago" />
 
     <org.schabi.newpipe.views.NewPipeTextView
         android:id="@+id/itemCommentContentView"
@@ -96,16 +96,4 @@
         android:visibility="gone"
         tools:visibility="visible" />
 
-    <org.schabi.newpipe.views.NewPipeTextView
-        android:id="@+id/itemPublishedTime"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/itemCommentContentView"
-        android:layout_marginLeft="12dp"
-        android:layout_toRightOf="@id/detail_heart_image_view"
-        android:lines="1"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textSize="@dimen/video_item_search_upload_date_text_size"
-        tools:text="1 year ago" />
-
 </RelativeLayout>
diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml
deleted file mode 100644
index 606a237c5..000000000
--- a/app/src/main/res/layout/list_comments_mini_item.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/itemRoot"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?attr/selectableItemBackground"
-    android:clickable="true"
-    android:focusable="true"
-    android:padding="@dimen/video_item_search_padding">
-
-    <com.google.android.material.imageview.ShapeableImageView
-        android:id="@+id/itemThumbnailView"
-        android:layout_width="42dp"
-        android:layout_height="42dp"
-        android:layout_centerVertical="true"
-        android:layout_marginStart="3dp"
-        android:layout_marginRight="15dp"
-        android:src="@drawable/placeholder_person"
-        app:shapeAppearance="@style/CircularImageView"
-        tools:ignore="RtlHardcoded" />
-
-
-    <org.schabi.newpipe.views.NewPipeTextView
-        android:id="@+id/itemCommentContentView"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
-        android:layout_toRightOf="@+id/itemThumbnailView"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textSize="@dimen/comment_item_content_text_size"
-        tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
-
-    <ImageView
-        android:id="@+id/detail_thumbs_up_img_view"
-        android:layout_width="@dimen/video_item_detail_like_image_width"
-        android:layout_height="@dimen/video_item_detail_like_image_height"
-        android:layout_below="@id/itemCommentContentView"
-        android:layout_toRightOf="@+id/itemThumbnailView"
-        android:contentDescription="@string/detail_likes_img_view_description"
-        android:src="@drawable/ic_thumb_up" />
-
-    <org.schabi.newpipe.views.NewPipeTextView
-        android:id="@+id/detail_thumbs_up_count_view"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/video_item_detail_like_image_height"
-        android:layout_below="@id/itemCommentContentView"
-        android:layout_marginLeft="@dimen/video_item_detail_like_margin"
-        android:layout_toRightOf="@id/detail_thumbs_up_img_view"
-        android:lines="1"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textSize="@dimen/video_item_detail_likes_text_size"
-        tools:ignore="RtlHardcoded"
-        tools:text="12M" />
-
-    <org.schabi.newpipe.views.NewPipeTextView
-        android:id="@+id/itemPublishedTime"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/itemCommentContentView"
-        android:layout_marginLeft="12dp"
-        android:layout_toRightOf="@id/detail_thumbs_up_count_view"
-        android:lines="1"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textSize="@dimen/video_item_search_upload_date_text_size"
-        tools:text="1 year ago" />
-
-</RelativeLayout>

From 4c709b2c4d1aaa6c8568e5f7845f314072fef643 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 10:58:32 +0200
Subject: [PATCH 042/141] Use start/end instead of left/right in comment layout

---
 app/src/main/res/layout/list_comment_item.xml | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/app/src/main/res/layout/list_comment_item.xml b/app/src/main/res/layout/list_comment_item.xml
index 26fcc9727..3e0cce2e7 100644
--- a/app/src/main/res/layout/list_comment_item.xml
+++ b/app/src/main/res/layout/list_comment_item.xml
@@ -15,10 +15,9 @@
         android:layout_width="42dp"
         android:layout_height="42dp"
         android:layout_alignParentStart="true"
-        android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
-        android:layout_marginLeft="3dp"
-        android:layout_marginRight="@dimen/comment_item_avatar_right_margin"
+        android:layout_marginStart="3dp"
+        android:layout_marginEnd="@dimen/comment_item_avatar_right_margin"
         android:focusable="false"
         android:src="@drawable/placeholder_person"
         app:shapeAppearance="@style/CircularImageView"
@@ -29,7 +28,7 @@
         android:layout_width="@dimen/video_item_detail_pinned_image_width"
         android:layout_height="@dimen/video_item_detail_pinned_image_height"
         android:layout_alignParentTop="true"
-        android:layout_marginRight="@dimen/video_item_detail_pinned_right_margin"
+        android:layout_marginEnd="@dimen/video_item_detail_pinned_right_margin"
         android:layout_toEndOf="@+id/itemThumbnailView"
         android:contentDescription="@string/detail_pinned_comment_view_description"
         android:src="@drawable/ic_pin"
@@ -56,18 +55,16 @@
         android:layout_below="@id/itemTitleView"
         android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
         android:layout_toEndOf="@+id/itemThumbnailView"
-        android:layout_toRightOf="@+id/itemThumbnailView"
         android:textAppearance="?android:attr/textAppearanceLarge"
         android:textSize="@dimen/comment_item_content_text_size"
         tools:text="Comment Content, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
 
-
     <ImageView
         android:id="@+id/detail_thumbs_up_img_view"
         android:layout_width="@dimen/video_item_detail_like_image_width"
         android:layout_height="@dimen/video_item_detail_like_image_height"
         android:layout_below="@id/itemCommentContentView"
-        android:layout_toRightOf="@+id/itemThumbnailView"
+        android:layout_toEndOf="@+id/itemThumbnailView"
         android:contentDescription="@string/detail_likes_img_view_description"
         android:src="@drawable/ic_thumb_up" />
 
@@ -76,8 +73,8 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/video_item_detail_like_image_height"
         android:layout_below="@id/itemCommentContentView"
-        android:layout_marginLeft="@dimen/video_item_detail_like_margin"
-        android:layout_toRightOf="@id/detail_thumbs_up_img_view"
+        android:layout_marginStart="@dimen/video_item_detail_like_margin"
+        android:layout_toEndOf="@id/detail_thumbs_up_img_view"
         android:lines="1"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/video_item_detail_likes_text_size"
@@ -89,8 +86,8 @@
         android:layout_width="@dimen/video_item_detail_heart_image_size"
         android:layout_height="@dimen/video_item_detail_heart_image_size"
         android:layout_below="@id/itemCommentContentView"
-        android:layout_marginLeft="@dimen/video_item_detail_heart_margin"
-        android:layout_toRightOf="@+id/detail_thumbs_up_count_view"
+        android:layout_marginStart="@dimen/video_item_detail_heart_margin"
+        android:layout_toEndOf="@+id/detail_thumbs_up_count_view"
         android:contentDescription="@string/detail_heart_img_view_description"
         android:src="@drawable/ic_heart"
         android:visibility="gone"

From 059db6fb31880b279ea30bc45c5048d7941cc30a Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 14:56:04 +0200
Subject: [PATCH 043/141] Add replies button to comments

---
 .../holder/CommentInfoItemHolder.java         | 56 ++++++++++++++-----
 .../org/schabi/newpipe/util/Localization.java |  5 ++
 app/src/main/res/layout/list_comment_item.xml | 38 ++++++++-----
 app/src/main/res/values/strings.xml           |  4 ++
 4 files changed, 74 insertions(+), 29 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index afe641c18..f1ac72e89 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -9,6 +9,7 @@ import android.text.style.URLSpan;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
@@ -57,15 +58,20 @@ public class CommentInfoItemHolder extends InfoItemHolder {
     private final RelativeLayout itemRoot;
     private final ImageView itemThumbnailView;
     private final TextView itemContentView;
+    private final ImageView itemThumbsUpView;
     private final TextView itemLikesCountView;
     private final TextView itemTitleView;
     private final ImageView itemHeartView;
     private final ImageView itemPinnedView;
+    private final Button repliesButton;
 
     private final CompositeDisposable disposables = new CompositeDisposable();
-    @Nullable private Description commentText;
-    @Nullable private StreamingService streamService;
-    @Nullable private String streamUrl;
+    @Nullable
+    private Description commentText;
+    @Nullable
+    private StreamingService streamService;
+    @Nullable
+    private String streamUrl;
 
     public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,
                                  final ViewGroup parent) {
@@ -74,10 +80,12 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         itemRoot = itemView.findViewById(R.id.itemRoot);
         itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
         itemContentView = itemView.findViewById(R.id.itemCommentContentView);
+        itemThumbsUpView = itemView.findViewById(R.id.detail_thumbs_up_img_view);
         itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
         itemTitleView = itemView.findViewById(R.id.itemTitleView);
         itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
         itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
+        repliesButton = itemView.findViewById(R.id.replies_button);
 
         commentHorizontalPadding = (int) infoItemBuilder.getContext()
                 .getResources().getDimension(R.dimen.comments_horizontal_padding);
@@ -97,6 +105,8 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         }
         final CommentsInfoItem item = (CommentsInfoItem) infoItem;
 
+
+        // load the author avatar
         PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
         if (ImageStrategy.shouldLoadImages()) {
             itemThumbnailView.setVisibility(View.VISIBLE);
@@ -109,6 +119,10 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         }
         itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
 
+
+        // setup the top row, with pinned icon, author name and comment date
+        itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
+
         final String uploadDate;
         if (item.getUploadDate() != null) {
             uploadDate = Localization.relativeTime(item.getUploadDate().offsetDateTime());
@@ -117,9 +131,29 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         }
         itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(), uploadDate));
 
-        itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
+
+        // setup bottom row, with likes, heart and replies button
+        if (item.getLikeCount() >= 0) {
+            itemLikesCountView.setText(
+                    Localization.shortCount(
+                            itemBuilder.getContext(),
+                            item.getLikeCount()));
+        } else {
+            itemLikesCountView.setText("-");
+        }
+
         itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
 
+        final boolean hasReplies = item.getReplies() != null;
+        repliesButton.setOnClickListener(hasReplies ? (v) -> openRepliesFragment() : null);
+        repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
+        repliesButton.setText(hasReplies
+                ? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");
+        ((RelativeLayout.LayoutParams) itemThumbsUpView.getLayoutParams()).topMargin =
+                hasReplies ? 0 : DeviceUtils.dpToPx(6, itemBuilder.getContext());
+
+
+        // setup comment content and click listeners to expand/ellipsize it
         try {
             streamService = NewPipe.getService(item.getServiceId());
         } catch (final ExtractionException e) {
@@ -135,16 +169,6 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         //noinspection ClickableViewAccessibility
         itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
 
-        if (item.getLikeCount() >= 0) {
-            itemLikesCountView.setText(
-                    Localization.shortCount(
-                            itemBuilder.getContext(),
-                            item.getLikeCount()));
-        } else {
-            itemLikesCountView.setText("-");
-        }
-
-
         itemView.setOnClickListener(view -> {
             toggleEllipsize();
             if (itemBuilder.getOnCommentsSelectedListener() != null) {
@@ -278,4 +302,8 @@ public class CommentInfoItemHolder extends InfoItemHolder {
                     onCompletion);
         }
     }
+
+    private void openRepliesFragment() {
+        // TODO
+    }
 }
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 c4034252d..20d21d76b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -209,6 +209,11 @@ public final class Localization {
                 deletedCount, shortCount(context, deletedCount));
     }
 
+    public static String replyCount(final Context context, final int replyCount) {
+        return getQuantity(context, R.plurals.replies, 0, replyCount,
+                String.valueOf(replyCount));
+    }
+
     public static String getDurationString(final long duration) {
         final String output;
 
diff --git a/app/src/main/res/layout/list_comment_item.xml b/app/src/main/res/layout/list_comment_item.xml
index 3e0cce2e7..631ab204b 100644
--- a/app/src/main/res/layout/list_comment_item.xml
+++ b/app/src/main/res/layout/list_comment_item.xml
@@ -16,7 +16,6 @@
         android:layout_height="42dp"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:layout_marginStart="3dp"
         android:layout_marginEnd="@dimen/comment_item_avatar_right_margin"
         android:focusable="false"
         android:src="@drawable/placeholder_person"
@@ -31,39 +30,37 @@
         android:layout_marginEnd="@dimen/video_item_detail_pinned_right_margin"
         android:layout_toEndOf="@+id/itemThumbnailView"
         android:contentDescription="@string/detail_pinned_comment_view_description"
-        android:src="@drawable/ic_pin"
-        android:visibility="gone"
-        tools:visibility="visible" />
+        android:src="@drawable/ic_pin" />
 
     <org.schabi.newpipe.views.NewPipeTextView
         android:id="@+id/itemTitleView"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
-        android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
         android:layout_toEndOf="@+id/detail_pinned_view"
         android:ellipsize="end"
         android:lines="1"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textSize="@dimen/comment_item_title_text_size"
-        tools:text="Author Name, Lorem ipsum · 5 months ago" />
+        tools:text="Author Name, Lorem ipsum • 5 months ago" />
 
     <org.schabi.newpipe.views.NewPipeTextView
         android:id="@+id/itemCommentContentView"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/itemTitleView"
-        android:layout_marginBottom="@dimen/channel_item_description_to_details_margin"
+        android:layout_marginTop="6dp"
         android:layout_toEndOf="@+id/itemThumbnailView"
         android:textAppearance="?android:attr/textAppearanceLarge"
         android:textSize="@dimen/comment_item_content_text_size"
-        tools:text="Comment Content, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
+        tools:text="@tools:sample/lorem/random[1]" />
 
     <ImageView
         android:id="@+id/detail_thumbs_up_img_view"
         android:layout_width="@dimen/video_item_detail_like_image_width"
         android:layout_height="@dimen/video_item_detail_like_image_height"
         android:layout_below="@id/itemCommentContentView"
+        android:layout_alignBottom="@+id/replies_button"
         android:layout_toEndOf="@+id/itemThumbnailView"
         android:contentDescription="@string/detail_likes_img_view_description"
         android:src="@drawable/ic_thumb_up" />
@@ -71,26 +68,37 @@
     <org.schabi.newpipe.views.NewPipeTextView
         android:id="@+id/detail_thumbs_up_count_view"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/video_item_detail_like_image_height"
-        android:layout_below="@id/itemCommentContentView"
+        android:layout_height="wrap_content"
+        android:layout_alignTop="@id/detail_thumbs_up_img_view"
+        android:layout_alignBottom="@id/detail_thumbs_up_img_view"
         android:layout_marginStart="@dimen/video_item_detail_like_margin"
         android:layout_toEndOf="@id/detail_thumbs_up_img_view"
+        android:gravity="center"
         android:lines="1"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/video_item_detail_likes_text_size"
-        tools:ignore="RtlHardcoded"
         tools:text="12M" />
 
     <ImageView
         android:id="@+id/detail_heart_image_view"
         android:layout_width="@dimen/video_item_detail_heart_image_size"
         android:layout_height="@dimen/video_item_detail_heart_image_size"
-        android:layout_below="@id/itemCommentContentView"
+        android:layout_alignTop="@id/detail_thumbs_up_img_view"
+        android:layout_alignBottom="@id/detail_thumbs_up_img_view"
         android:layout_marginStart="@dimen/video_item_detail_heart_margin"
         android:layout_toEndOf="@+id/detail_thumbs_up_count_view"
         android:contentDescription="@string/detail_heart_img_view_description"
-        android:src="@drawable/ic_heart"
-        android:visibility="gone"
-        tools:visibility="visible" />
+        android:src="@drawable/ic_heart" />
+
+    <Button
+        android:id="@+id/replies_button"
+        style="?android:attr/borderlessButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/itemCommentContentView"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="@dimen/video_item_detail_heart_margin"
+        android:minHeight="0dp"
+        tools:text="543 replies" />
 
 </RelativeLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 435e9a382..d94abfe70 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -838,4 +838,8 @@
     <string name="share_playlist_with_list">Share URL list</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
     <string name="share_playlist_content_details">%1$s\n%2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s reply</item>
+        <item quantity="other">%s replies</item>
+    </plurals>
 </resources>

From 5d350aec873c123a133aef6ce24abdb3dae6e8c4 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 15:25:17 +0200
Subject: [PATCH 044/141] Move RelatedItemInfo next to fragment using it

---
 .../{util => fragments/list/videos}/RelatedItemInfo.java | 9 +++++----
 .../fragments/list/videos/RelatedItemsFragment.java      | 1 -
 2 files changed, 5 insertions(+), 5 deletions(-)
 rename app/src/main/java/org/schabi/newpipe/{util => fragments/list/videos}/RelatedItemInfo.java (76%)

diff --git a/app/src/main/java/org/schabi/newpipe/util/RelatedItemInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
similarity index 76%
rename from app/src/main/java/org/schabi/newpipe/util/RelatedItemInfo.java
rename to app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
index f96bb0d54..9d2985082 100644
--- a/app/src/main/java/org/schabi/newpipe/util/RelatedItemInfo.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.fragments.list.videos;
 
 import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.ListInfo;
@@ -9,9 +9,10 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-public class RelatedItemInfo extends ListInfo<InfoItem> {
-    public RelatedItemInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
-                           final String name) {
+public final class RelatedItemInfo extends ListInfo<InfoItem> {
+    private RelatedItemInfo(final int serviceId,
+                            final ListLinkHandler listUrlIdHandler,
+                            final String name) {
         super(serviceId, listUrlIdHandler, name);
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
index 65664e0e3..5ec316420 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
@@ -21,7 +21,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.info_list.ItemViewMode;
 import org.schabi.newpipe.ktx.ViewUtils;
-import org.schabi.newpipe.util.RelatedItemInfo;
 
 import java.io.Serializable;
 import java.util.function.Supplier;

From 4f7b36cd70a1f0a6104713608f12d3ceb6fa6b69 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 22:26:35 +0200
Subject: [PATCH 045/141] Keep source list info in InfoItemBuilder

Also remove some unused code
---
 .../fragments/list/BaseListInfoFragment.java  |  6 ++
 .../newpipe/info_list/InfoItemBuilder.java    | 60 ++++---------------
 .../newpipe/info_list/InfoListAdapter.java    |  5 ++
 3 files changed, 23 insertions(+), 48 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index e7e9f5aad..cf09d083e 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -66,6 +66,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
     @Override
     public void onResume() {
         super.onResume();
+
         // Check if it was loading when the fragment was stopped/paused,
         if (wasLoading.getAndSet(false)) {
             if (hasMoreItems() && !infoListAdapter.getItemsList().isEmpty()) {
@@ -74,6 +75,8 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
                 doInitialLoadLogic();
             }
         }
+
+        infoListAdapter.setSourceListInfo(currentInfo);
     }
 
     @Override
@@ -137,6 +140,8 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
         infoListAdapter.clearStreamItemList();
 
         currentInfo = null;
+        infoListAdapter.setSourceListInfo(null);
+
         if (currentWorker != null) {
             currentWorker.dispose();
         }
@@ -146,6 +151,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
                 .subscribe((@NonNull L result) -> {
                     isLoading.set(false);
                     currentInfo = result;
+                    infoListAdapter.setSourceListInfo(result);
                     currentNextPage = result.getNextPage();
                     handleResult(result);
                 }, throwable ->
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
index d959c6327..3b66fa648 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -1,25 +1,13 @@
 package org.schabi.newpipe.info_list;
 
 import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
 
-import androidx.annotation.NonNull;
-
-import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.Info;
+import org.schabi.newpipe.extractor.ListInfo;
 import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
-import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.InfoItemHolder;
-import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
-import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.OnClickGesture;
 
 /*
@@ -54,44 +42,12 @@ public class InfoItemBuilder {
     private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
     private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
 
+    private ListInfo<?> sourceListInfo; // the list-info the info-items from this list belong to
+
     public InfoItemBuilder(final Context context) {
         this.context = context;
     }
 
-    public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
-                          final HistoryRecordManager historyRecordManager) {
-        return buildView(parent, infoItem, historyRecordManager, false);
-    }
-
-    public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
-                          final HistoryRecordManager historyRecordManager,
-                          final boolean useMiniVariant) {
-        final InfoItemHolder holder =
-                holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
-        holder.updateFromItem(infoItem, historyRecordManager);
-        return holder.itemView;
-    }
-
-    private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent,
-                                              @NonNull final InfoItem.InfoType infoType,
-                                              final boolean useMiniVariant) {
-        switch (infoType) {
-            case STREAM:
-                return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent)
-                        : new StreamInfoItemHolder(this, parent);
-            case CHANNEL:
-                return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent)
-                        : new ChannelInfoItemHolder(this, parent);
-            case PLAYLIST:
-                return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
-                        : new PlaylistInfoItemHolder(this, parent);
-            case COMMENT:
-                return new CommentInfoItemHolder(this, parent);
-            default:
-                throw new RuntimeException("InfoType not expected = " + infoType.name());
-        }
-    }
-
     public Context getContext() {
         return context;
     }
@@ -128,4 +84,12 @@ public class InfoItemBuilder {
             final OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
         this.onCommentsSelectedListener = onCommentsSelectedListener;
     }
+
+    public Info getSourceListInfo() {
+        return sourceListInfo;
+    }
+
+    public void setSourceListInfo(final ListInfo<?> sourceListInfo) {
+        this.sourceListInfo = sourceListInfo;
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index 575568c00..902c4665c 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
 
 import org.schabi.newpipe.databinding.PignateFooterBinding;
 import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.ListInfo;
 import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
@@ -115,6 +116,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
         infoItemBuilder.setOnCommentsSelectedListener(listener);
     }
 
+    public void setSourceListInfo(final ListInfo<?> sourceInfo) {
+        infoItemBuilder.setSourceListInfo(sourceInfo);
+    }
+
     public void setUseMiniVariant(final boolean useMiniVariant) {
         this.useMiniVariant = useMiniVariant;
     }

From 0dd4553700d90918cb4a91aac037bc437b4d1976 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 22:28:41 +0200
Subject: [PATCH 046/141] Load more items even if initial related items are
 empty

---
 .../org/schabi/newpipe/fragments/list/BaseListInfoFragment.java | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index cf09d083e..715512e02 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -237,6 +237,8 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
             if (!result.getRelatedItems().isEmpty()) {
                 infoListAdapter.addInfoItemList(result.getRelatedItems());
                 showListFooter(hasMoreItems());
+            } else if (hasMoreItems()) {
+                loadMoreItems();
             } else {
                 infoListAdapter.clearStreamItemList();
                 showEmptyState();

From f9494a294f4b31547d4bb664489d2503f1b5cfe8 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 22:28:48 +0200
Subject: [PATCH 047/141] Implement CommentRepliesFragment

---
 .../org/schabi/newpipe/error/UserAction.java  |  1 +
 .../list/comments/CommentRepliesFragment.java | 54 +++++++++++++++++++
 .../list/comments/CommentRepliesInfo.java     | 25 +++++++++
 .../holder/CommentInfoItemHolder.java         | 18 +++++--
 4 files changed, 95 insertions(+), 3 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
 create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java

diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java
index 976173373..c8701cd77 100644
--- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java
+++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java
@@ -19,6 +19,7 @@ public enum UserAction {
     REQUESTED_PLAYLIST("requested playlist"),
     REQUESTED_KIOSK("requested kiosk"),
     REQUESTED_COMMENTS("requested comments"),
+    REQUESTED_COMMENT_REPLIES("requested comment replies"),
     REQUESTED_FEED("requested feed"),
     REQUESTED_BOOKMARK("bookmark"),
     DELETE_FROM_HISTORY("delete from history"),
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
new file mode 100644
index 000000000..1a3508ff3
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -0,0 +1,54 @@
+package org.schabi.newpipe.fragments.list.comments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.error.UserAction;
+import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.comments.CommentsInfo;
+import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
+import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
+import org.schabi.newpipe.util.ExtractorHelper;
+
+import io.reactivex.rxjava3.core.Single;
+
+public final class CommentRepliesFragment
+        extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
+
+    // has the same content as super.currentInfo, except that it's never null
+    private final CommentRepliesInfo currentInfo;
+    // the original comments info loaded alongside stream
+    private final CommentsInfo commentsInfo;
+
+    public CommentRepliesFragment(final CommentsInfo commentsInfo,
+                                  final CommentsInfoItem commentsInfoItem) {
+        super(UserAction.REQUESTED_COMMENT_REPLIES);
+        this.currentInfo = CommentRepliesInfo.getInfo(commentsInfoItem);
+        this.commentsInfo = commentsInfo;
+        setInitialData(commentsInfo.getServiceId(), commentsInfo.getUrl(), commentsInfo.getName());
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull final LayoutInflater inflater,
+                             @Nullable final ViewGroup container,
+                             @Nullable final Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_comments, container, false);
+    }
+
+    @Override
+    protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
+        return Single.just(this.currentInfo);
+    }
+
+    @Override
+    protected Single<ListExtractor.InfoItemsPage<CommentsInfoItem>> loadMoreItemsLogic() {
+        return ExtractorHelper.getMoreCommentItems(serviceId, commentsInfo, currentNextPage);
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
new file mode 100644
index 000000000..25484296b
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
@@ -0,0 +1,25 @@
+package org.schabi.newpipe.fragments.list.comments;
+
+import org.schabi.newpipe.extractor.ListInfo;
+import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
+import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
+
+import java.util.Collections;
+
+public final class CommentRepliesInfo extends ListInfo<CommentsInfoItem> {
+    private CommentRepliesInfo(final int serviceId,
+                               final ListLinkHandler listUrlIdHandler,
+                               final String name) {
+        super(serviceId, listUrlIdHandler, name);
+    }
+
+    public static CommentRepliesInfo getInfo(final CommentsInfoItem comment) {
+        final ListLinkHandler handler =
+                new ListLinkHandler("", "", "", Collections.emptyList(), null);
+        final CommentRepliesInfo relatedItemInfo = new CommentRepliesInfo(
+                comment.getServiceId(), handler, comment.getName());
+        relatedItemInfo.setNextPage(comment.getReplies());
+        relatedItemInfo.setRelatedItems(Collections.emptyList()); // since it must be non-null
+        return relatedItemInfo;
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index f1ac72e89..cbc7096bf 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -18,15 +18,18 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.text.HtmlCompat;
 
+import org.schabi.newpipe.MainActivity;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.ServiceList;
 import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.Description;
+import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
 import org.schabi.newpipe.info_list.InfoItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.DeviceUtils;
@@ -145,7 +148,7 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
 
         final boolean hasReplies = item.getReplies() != null;
-        repliesButton.setOnClickListener(hasReplies ? (v) -> openRepliesFragment() : null);
+        repliesButton.setOnClickListener(hasReplies ? (v) -> openRepliesFragment(item) : null);
         repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
         repliesButton.setText(hasReplies
                 ? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");
@@ -303,7 +306,16 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         }
     }
 
-    private void openRepliesFragment() {
-        // TODO
+    private void openRepliesFragment(final CommentsInfoItem commentsInfoItem) {
+        ((MainActivity) itemBuilder.getContext())
+                .getSupportFragmentManager()
+                .beginTransaction()
+                .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
+                        R.animator.custom_fade_in, R.animator.custom_fade_out)
+                .replace(R.id.fragment_holder,
+                        new CommentRepliesFragment((CommentsInfo) itemBuilder.getSourceListInfo(),
+                                commentsInfoItem))
+                .addToBackStack(null)
+                .commit();
     }
 }

From 78cf9aaa7d4c8660cc25953def1e6346bbe268a5 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 23:07:58 +0200
Subject: [PATCH 048/141] Save and restore state in CommentRepliesFragment

---
 .../list/comments/CommentRepliesFragment.java | 41 +++++++++++++++++--
 1 file changed, 38 insertions(+), 3 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 1a3508ff3..da8017963 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -16,19 +16,30 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.util.ExtractorHelper;
 
+import java.util.Queue;
+
 import io.reactivex.rxjava3.core.Single;
 
 public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
 
     // has the same content as super.currentInfo, except that it's never null
-    private final CommentRepliesInfo currentInfo;
+    private CommentRepliesInfo currentInfo;
     // the original comments info loaded alongside stream
-    private final CommentsInfo commentsInfo;
+    private CommentsInfo commentsInfo;
+
+
+    /*//////////////////////////////////////////////////////////////////////////
+    // Constructors and lifecycle
+    //////////////////////////////////////////////////////////////////////////*/
+
+    public CommentRepliesFragment() {
+        super(UserAction.REQUESTED_COMMENT_REPLIES);
+    }
 
     public CommentRepliesFragment(final CommentsInfo commentsInfo,
                                   final CommentsInfoItem commentsInfoItem) {
-        super(UserAction.REQUESTED_COMMENT_REPLIES);
+        this();
         this.currentInfo = CommentRepliesInfo.getInfo(commentsInfoItem);
         this.commentsInfo = commentsInfo;
         setInitialData(commentsInfo.getServiceId(), commentsInfo.getUrl(), commentsInfo.getName());
@@ -42,6 +53,30 @@ public final class CommentRepliesFragment
         return inflater.inflate(R.layout.fragment_comments, container, false);
     }
 
+
+    /*//////////////////////////////////////////////////////////////////////////
+    // State Saving
+    //////////////////////////////////////////////////////////////////////////*/
+
+    @Override
+    public void writeTo(final Queue<Object> objectsToSave) {
+        super.writeTo(objectsToSave);
+        objectsToSave.add(currentInfo);
+        objectsToSave.add(commentsInfo);
+    }
+
+    @Override
+    public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
+        super.readFrom(savedObjects);
+        currentInfo = (CommentRepliesInfo) savedObjects.poll();
+        commentsInfo = (CommentsInfo) savedObjects.poll();
+    }
+
+
+    /*//////////////////////////////////////////////////////////////////////////
+    // Data loading
+    //////////////////////////////////////////////////////////////////////////*/
+
     @Override
     protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
         return Single.just(this.currentInfo);

From 591ed2e01fab18ea718876f2a7a6793bbb0f2c7e Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 11 Apr 2023 23:12:17 +0200
Subject: [PATCH 049/141] Fix some code smells

---
 .../list/comments/CommentRepliesFragment.java          | 10 +++++-----
 .../info_list/holder/CommentInfoItemHolder.java        |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index da8017963..8cd8222d2 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -24,7 +24,7 @@ public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
 
     // has the same content as super.currentInfo, except that it's never null
-    private CommentRepliesInfo currentInfo;
+    private CommentRepliesInfo commentRepliesInfo;
     // the original comments info loaded alongside stream
     private CommentsInfo commentsInfo;
 
@@ -40,7 +40,7 @@ public final class CommentRepliesFragment
     public CommentRepliesFragment(final CommentsInfo commentsInfo,
                                   final CommentsInfoItem commentsInfoItem) {
         this();
-        this.currentInfo = CommentRepliesInfo.getInfo(commentsInfoItem);
+        this.commentRepliesInfo = CommentRepliesInfo.getInfo(commentsInfoItem);
         this.commentsInfo = commentsInfo;
         setInitialData(commentsInfo.getServiceId(), commentsInfo.getUrl(), commentsInfo.getName());
     }
@@ -61,14 +61,14 @@ public final class CommentRepliesFragment
     @Override
     public void writeTo(final Queue<Object> objectsToSave) {
         super.writeTo(objectsToSave);
-        objectsToSave.add(currentInfo);
+        objectsToSave.add(commentRepliesInfo);
         objectsToSave.add(commentsInfo);
     }
 
     @Override
     public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
         super.readFrom(savedObjects);
-        currentInfo = (CommentRepliesInfo) savedObjects.poll();
+        commentRepliesInfo = (CommentRepliesInfo) savedObjects.poll();
         commentsInfo = (CommentsInfo) savedObjects.poll();
     }
 
@@ -79,7 +79,7 @@ public final class CommentRepliesFragment
 
     @Override
     protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
-        return Single.just(this.currentInfo);
+        return Single.just(this.commentRepliesInfo);
     }
 
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index cbc7096bf..74c80c73d 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -148,7 +148,7 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
 
         final boolean hasReplies = item.getReplies() != null;
-        repliesButton.setOnClickListener(hasReplies ? (v) -> openRepliesFragment(item) : null);
+        repliesButton.setOnClickListener(hasReplies ? v -> openRepliesFragment(item) : null);
         repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
         repliesButton.setText(hasReplies
                 ? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");

From 94ea329b50ef367babdd83624af14d2103ca7069 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 08:39:33 +0200
Subject: [PATCH 050/141] Always show comment replies in list mode

---
 .../list/comments/CommentRepliesFragment.java       | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 8cd8222d2..4b85dfa68 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -2,6 +2,8 @@ package org.schabi.newpipe.fragments.list.comments;
 
 import android.os.Bundle;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -14,6 +16,7 @@ import org.schabi.newpipe.extractor.ListExtractor;
 import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
+import org.schabi.newpipe.info_list.ItemViewMode;
 import org.schabi.newpipe.util.ExtractorHelper;
 
 import java.util.Queue;
@@ -86,4 +89,14 @@ public final class CommentRepliesFragment
     protected Single<ListExtractor.InfoItemsPage<CommentsInfoItem>> loadMoreItemsLogic() {
         return ExtractorHelper.getMoreCommentItems(serviceId, commentsInfo, currentNextPage);
     }
+
+
+    /*//////////////////////////////////////////////////////////////////////////
+    // Utils
+    //////////////////////////////////////////////////////////////////////////*/
+
+    @Override
+    protected ItemViewMode getItemViewMode() {
+        return ItemViewMode.LIST;
+    }
 }

From 4b6392df5420f16e5e0234ce88cdf0e28ddd6ce3 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 08:54:58 +0200
Subject: [PATCH 051/141] Set comment replies fragment title

---
 .../list/comments/CommentRepliesFragment.java | 21 ++++++++++---------
 .../list/comments/CommentRepliesInfo.java     |  4 ++--
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 4b85dfa68..783fd18ba 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -2,8 +2,6 @@ package org.schabi.newpipe.fragments.list.comments;
 
 import android.os.Bundle;
 import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -18,6 +16,7 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.info_list.ItemViewMode;
 import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.Localization;
 
 import java.util.Queue;
 
@@ -26,10 +25,10 @@ import io.reactivex.rxjava3.core.Single;
 public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
 
-    // has the same content as super.currentInfo, except that it's never null
-    private CommentRepliesInfo commentRepliesInfo;
-    // the original comments info loaded alongside stream
+    // the original comments info loaded alongside the stream
     private CommentsInfo commentsInfo;
+    // the comment to show replies of
+    private CommentsInfoItem commentsInfoItem;
 
 
     /*//////////////////////////////////////////////////////////////////////////
@@ -43,8 +42,8 @@ public final class CommentRepliesFragment
     public CommentRepliesFragment(final CommentsInfo commentsInfo,
                                   final CommentsInfoItem commentsInfoItem) {
         this();
-        this.commentRepliesInfo = CommentRepliesInfo.getInfo(commentsInfoItem);
         this.commentsInfo = commentsInfo;
+        this.commentsInfoItem = commentsInfoItem;
         setInitialData(commentsInfo.getServiceId(), commentsInfo.getUrl(), commentsInfo.getName());
     }
 
@@ -58,21 +57,21 @@ public final class CommentRepliesFragment
 
 
     /*//////////////////////////////////////////////////////////////////////////
-    // State Saving
+    // State saving
     //////////////////////////////////////////////////////////////////////////*/
 
     @Override
     public void writeTo(final Queue<Object> objectsToSave) {
         super.writeTo(objectsToSave);
-        objectsToSave.add(commentRepliesInfo);
         objectsToSave.add(commentsInfo);
+        objectsToSave.add(commentsInfoItem);
     }
 
     @Override
     public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
         super.readFrom(savedObjects);
-        commentRepliesInfo = (CommentRepliesInfo) savedObjects.poll();
         commentsInfo = (CommentsInfo) savedObjects.poll();
+        commentsInfoItem = (CommentsInfoItem) savedObjects.poll();
     }
 
 
@@ -82,7 +81,9 @@ public final class CommentRepliesFragment
 
     @Override
     protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
-        return Single.just(this.commentRepliesInfo);
+        return Single.fromCallable(() -> CommentRepliesInfo.getInfo(commentsInfoItem,
+                // the reply count string will be shown as the activity title
+                Localization.replyCount(requireContext(), commentsInfoItem.getReplyCount())));
     }
 
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
index 25484296b..7f85dc6bf 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
@@ -13,11 +13,11 @@ public final class CommentRepliesInfo extends ListInfo<CommentsInfoItem> {
         super(serviceId, listUrlIdHandler, name);
     }
 
-    public static CommentRepliesInfo getInfo(final CommentsInfoItem comment) {
+    public static CommentRepliesInfo getInfo(final CommentsInfoItem comment, final String name) {
         final ListLinkHandler handler =
                 new ListLinkHandler("", "", "", Collections.emptyList(), null);
         final CommentRepliesInfo relatedItemInfo = new CommentRepliesInfo(
-                comment.getServiceId(), handler, comment.getName());
+                comment.getServiceId(), handler, name); // the name will be shown as fragment title
         relatedItemInfo.setNextPage(comment.getReplies());
         relatedItemInfo.setRelatedItems(Collections.emptyList()); // since it must be non-null
         return relatedItemInfo;

From ad68f784aebf44323f2c1234f5634ab10b95c517 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 10:54:03 +0200
Subject: [PATCH 052/141] Extract some utility methods from
 CommentInfoItemHolder

---
 .../holder/CommentInfoItemHolder.java         | 76 ++++---------------
 .../holder/StreamInfoItemHolder.java          | 23 +-----
 .../org/schabi/newpipe/util/Localization.java | 25 ++++++
 .../schabi/newpipe/util/NavigationHelper.java | 29 +++++++
 .../schabi/newpipe/util/ServiceHelper.java    |  8 ++
 5 files changed, 80 insertions(+), 81 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index 74c80c73d..03f796f59 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -1,12 +1,12 @@
 package org.schabi.newpipe.info_list.holder;
 
 import static android.text.TextUtils.isEmpty;
+import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
 
 import android.graphics.Paint;
 import android.text.Layout;
 import android.text.method.LinkMovementMethod;
 import android.text.style.URLSpan;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -15,21 +15,15 @@ import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.text.HtmlCompat;
+import androidx.fragment.app.FragmentActivity;
 
-import org.schabi.newpipe.MainActivity;
 import org.schabi.newpipe.R;
-import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.ServiceList;
 import org.schabi.newpipe.extractor.StreamingService;
 import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.Description;
-import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
 import org.schabi.newpipe.info_list.InfoItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.DeviceUtils;
@@ -46,7 +40,6 @@ import java.util.function.Consumer;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
 public class CommentInfoItemHolder extends InfoItemHolder {
-    private static final String TAG = "CommentIIHolder";
     private static final String ELLIPSIS = "…";
 
     private static final int COMMENT_DEFAULT_LINES = 2;
@@ -125,30 +118,19 @@ public class CommentInfoItemHolder extends InfoItemHolder {
 
         // setup the top row, with pinned icon, author name and comment date
         itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
-
-        final String uploadDate;
-        if (item.getUploadDate() != null) {
-            uploadDate = Localization.relativeTime(item.getUploadDate().offsetDateTime());
-        } else {
-            uploadDate = item.getTextualUploadDate();
-        }
-        itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(), uploadDate));
+        itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(),
+                Localization.relativeTimeOrTextual(item.getUploadDate(),
+                        item.getTextualUploadDate(), itemBuilder.getContext())));
 
 
         // setup bottom row, with likes, heart and replies button
-        if (item.getLikeCount() >= 0) {
-            itemLikesCountView.setText(
-                    Localization.shortCount(
-                            itemBuilder.getContext(),
-                            item.getLikeCount()));
-        } else {
-            itemLikesCountView.setText("-");
-        }
+        itemLikesCountView.setText(
+                Localization.likeCount(itemBuilder.getContext(), item.getLikeCount()));
 
         itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
 
         final boolean hasReplies = item.getReplies() != null;
-        repliesButton.setOnClickListener(hasReplies ? v -> openRepliesFragment(item) : null);
+        repliesButton.setOnClickListener(hasReplies ? v -> openCommentReplies(item) : null);
         repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
         repliesButton.setText(hasReplies
                 ? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");
@@ -157,14 +139,7 @@ public class CommentInfoItemHolder extends InfoItemHolder {
 
 
         // setup comment content and click listeners to expand/ellipsize it
-        try {
-            streamService = NewPipe.getService(item.getServiceId());
-        } catch (final ExtractionException e) {
-            // should never happen
-            ErrorUtil.showUiErrorSnackbar(itemBuilder.getContext(), "Getting StreamingService", e);
-            Log.w(TAG, "Cannot obtain service from comment service id, defaulting to YouTube", e);
-            streamService = ServiceList.YouTube;
-        }
+        streamService = getServiceById(item.getServiceId());
         streamUrl = item.getUrl();
         commentText = item.getCommentText();
         ellipsize();
@@ -193,19 +168,13 @@ public class CommentInfoItemHolder extends InfoItemHolder {
     }
 
     private void openCommentAuthor(final CommentsInfoItem item) {
-        if (isEmpty(item.getUploaderUrl())) {
-            return;
-        }
-        final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
-        try {
-            NavigationHelper.openChannelFragment(
-                    activity.getSupportFragmentManager(),
-                    item.getServiceId(),
-                    item.getUploaderUrl(),
-                    item.getUploaderName());
-        } catch (final Exception e) {
-            ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
-        }
+        NavigationHelper.openCommentAuthorIfPresent((FragmentActivity) itemBuilder.getContext(),
+                item);
+    }
+
+    private void openCommentReplies(final CommentsInfoItem item) {
+        NavigationHelper.openCommentRepliesFragment((FragmentActivity) itemBuilder.getContext(),
+                (CommentsInfo) itemBuilder.getSourceListInfo(), item);
     }
 
     private void allowLinkFocus() {
@@ -305,17 +274,4 @@ public class CommentInfoItemHolder extends InfoItemHolder {
                     onCompletion);
         }
     }
-
-    private void openRepliesFragment(final CommentsInfoItem commentsInfoItem) {
-        ((MainActivity) itemBuilder.getContext())
-                .getSupportFragmentManager()
-                .beginTransaction()
-                .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
-                        R.animator.custom_fade_in, R.animator.custom_fade_out)
-                .replace(R.id.fragment_holder,
-                        new CommentRepliesFragment((CommentsInfo) itemBuilder.getSourceListInfo(),
-                                commentsInfoItem))
-                .addToBackStack(null)
-                .commit();
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
index a84c98404..7f80707bc 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
@@ -12,10 +12,6 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.Localization;
 
-import androidx.preference.PreferenceManager;
-
-import static org.schabi.newpipe.MainActivity.DEBUG;
-
 /*
  * Created by Christian Schabesberger on 01.08.16.
  * <p>
@@ -81,7 +77,8 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
             }
         }
 
-        final String uploadDate = getFormattedRelativeUploadDate(infoItem);
+        final String uploadDate = Localization.relativeTimeOrTextual(infoItem.getUploadDate(),
+                infoItem.getTextualUploadDate(), itemBuilder.getContext());
         if (!TextUtils.isEmpty(uploadDate)) {
             if (viewsAndDate.isEmpty()) {
                 return uploadDate;
@@ -92,20 +89,4 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
 
         return viewsAndDate;
     }
-
-    private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
-        if (infoItem.getUploadDate() != null) {
-            String formattedRelativeTime = Localization
-                    .relativeTime(infoItem.getUploadDate().offsetDateTime());
-
-            if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
-                    .getBoolean(itemBuilder.getContext()
-                            .getString(R.string.show_original_time_ago_key), false)) {
-                formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
-            }
-            return formattedRelativeTime;
-        } else {
-            return infoItem.getTextualUploadDate();
-        }
-    }
 }
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 20d21d76b..dea48651e 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -1,5 +1,7 @@
 package org.schabi.newpipe.util;
 
+import static org.schabi.newpipe.MainActivity.DEBUG;
+
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -22,6 +24,7 @@ import org.ocpsoft.prettytime.units.Decade;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.extractor.ListExtractor;
 import org.schabi.newpipe.extractor.localization.ContentCountry;
+import org.schabi.newpipe.extractor.localization.DateWrapper;
 import org.schabi.newpipe.extractor.stream.AudioStream;
 import org.schabi.newpipe.extractor.stream.AudioTrackType;
 
@@ -214,6 +217,14 @@ public final class Localization {
                 String.valueOf(replyCount));
     }
 
+    public static String likeCount(final Context context, final int likeCount) {
+        if (likeCount < 0) {
+            return "-";
+        } else {
+            return shortCount(context, likeCount);
+        }
+    }
+
     public static String getDurationString(final long duration) {
         final String output;
 
@@ -333,6 +344,20 @@ public final class Localization {
         return prettyTime.formatUnrounded(offsetDateTime);
     }
 
+    public static String relativeTimeOrTextual(final DateWrapper parsed,
+                                               final String textual,
+                                               @Nullable final Context context) {
+        if (parsed == null) {
+            return textual;
+        } else if (DEBUG && context != null && PreferenceManager
+                .getDefaultSharedPreferences(context)
+                .getBoolean(context.getString(R.string.show_original_time_ago_key), false)) {
+            return relativeTime(parsed.offsetDateTime()) + " (" + textual + ")";
+        } else {
+            return relativeTime(parsed.offsetDateTime());
+        }
+    }
+
     public static void assureCorrectAppLanguage(final Context c) {
         final Resources res = c.getResources();
         final DisplayMetrics dm = res.getDisplayMetrics();
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index b0d7dcf73..e127a8b55 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -1,5 +1,6 @@
 package org.schabi.newpipe.util;
 
+import static android.text.TextUtils.isEmpty;
 import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
 
 import android.annotation.SuppressLint;
@@ -17,6 +18,7 @@ import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.content.ContextCompat;
 import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 
@@ -29,8 +31,11 @@ import org.schabi.newpipe.RouterActivity;
 import org.schabi.newpipe.about.AboutActivity;
 import org.schabi.newpipe.database.feed.model.FeedGroupEntity;
 import org.schabi.newpipe.download.DownloadActivity;
+import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.comments.CommentsInfo;
+import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.AudioStream;
 import org.schabi.newpipe.extractor.stream.DeliveryMethod;
@@ -41,6 +46,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
 import org.schabi.newpipe.fragments.MainFragment;
 import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
 import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
+import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
 import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
 import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
 import org.schabi.newpipe.fragments.list.search.SearchFragment;
@@ -476,6 +482,29 @@ public final class NavigationHelper {
                 item.getServiceId(), uploaderUrl, item.getUploaderName());
     }
 
+    public static void openCommentAuthorIfPresent(@NonNull final FragmentActivity activity,
+                                                  final CommentsInfoItem comment) {
+        if (isEmpty(comment.getUploaderUrl())) {
+            return;
+        }
+        try {
+            openChannelFragment(activity.getSupportFragmentManager(), comment.getServiceId(),
+                    comment.getUploaderUrl(), comment.getUploaderName());
+        } catch (final Exception e) {
+            ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
+        }
+    }
+
+    public static void openCommentRepliesFragment(@NonNull final FragmentActivity activity,
+                                                  final CommentsInfo commentsInfo,
+                                                  final CommentsInfoItem commentsInfoItem) {
+        defaultTransaction(activity.getSupportFragmentManager())
+                .replace(R.id.fragment_holder,
+                        new CommentRepliesFragment(commentsInfo, commentsInfoItem))
+                .addToBackStack(null)
+                .commit();
+    }
+
     public static void openPlaylistFragment(final FragmentManager fragmentManager,
                                             final int serviceId, final String url,
                                             @NonNull final String name) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
index acd019ba0..68905e6c9 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -144,6 +144,14 @@ public final class ServiceHelper {
                 .orElse("<unknown>");
     }
 
+    @NonNull
+    public static StreamingService getServiceById(final int serviceId) {
+        return ServiceList.all().stream()
+                .filter(s -> s.getServiceId() == serviceId)
+                .findFirst()
+                .orElseThrow();
+    }
+
     public static void setSelectedServiceId(final Context context, final int serviceId) {
         String serviceName;
         try {

From f41ab8b08699974b3c05c1dc5194b276b703172c Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 10:54:28 +0200
Subject: [PATCH 053/141] Add comment replies fragment header

---
 .../list/comments/CommentRepliesFragment.java |  59 ++++++++
 .../res/layout/comment_replies_header.xml     | 137 ++++++++++++++++++
 2 files changed, 196 insertions(+)
 create mode 100644 app/src/main/res/layout/comment_replies_header.xml

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 783fd18ba..163e7d25f 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -1,5 +1,7 @@
 package org.schabi.newpipe.fragments.list.comments;
 
+import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
+
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -7,20 +9,30 @@ import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.text.HtmlCompat;
 
 import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.CommentRepliesHeaderBinding;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.ListExtractor;
 import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.info_list.ItemViewMode;
+import org.schabi.newpipe.util.DeviceUtils;
 import org.schabi.newpipe.util.ExtractorHelper;
 import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.image.ImageStrategy;
+import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.text.TextLinkifier;
 
 import java.util.Queue;
+import java.util.function.Supplier;
 
 import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
 public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
@@ -29,6 +41,7 @@ public final class CommentRepliesFragment
     private CommentsInfo commentsInfo;
     // the comment to show replies of
     private CommentsInfoItem commentsInfoItem;
+    private final CompositeDisposable disposables = new CompositeDisposable();
 
 
     /*//////////////////////////////////////////////////////////////////////////
@@ -55,6 +68,52 @@ public final class CommentRepliesFragment
         return inflater.inflate(R.layout.fragment_comments, container, false);
     }
 
+    @Override
+    public void onDestroyView() {
+        disposables.clear();
+        super.onDestroyView();
+    }
+
+    @Override
+    protected Supplier<View> getListHeaderSupplier() {
+        return () -> {
+            final CommentRepliesHeaderBinding binding = CommentRepliesHeaderBinding
+                    .inflate(activity.getLayoutInflater(), itemsList, false);
+            final CommentsInfoItem item = commentsInfoItem;
+
+            // load the author avatar
+            PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
+            binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
+                    ? View.VISIBLE : View.GONE);
+
+            // setup author name and comment date
+            binding.authorName.setText(item.getUploaderName());
+            binding.uploadDate.setText(Localization.relativeTimeOrTextual(
+                    item.getUploadDate(), item.getTextualUploadDate(), getContext()));
+            binding.authorTouchArea.setOnClickListener(
+                    v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
+
+            // setup like count, hearted and pinned
+            binding.thumbsUpCount.setText(
+                    Localization.likeCount(requireContext(), item.getLikeCount()));
+            // for heartImage goneMarginEnd was used, but there is no way to tell ConstraintLayout
+            // not to use a different margin only when both the next two views are gone
+            ((ConstraintLayout.LayoutParams) binding.thumbsUpCount.getLayoutParams())
+                    .setMarginEnd(DeviceUtils.dpToPx(
+                            (item.isHeartedByUploader() || item.isPinned() ? 8 : 16),
+                            requireContext()));
+            binding.heartImage.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
+            binding.pinnedImage.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
+
+            // setup comment content
+            TextLinkifier.fromDescription(binding.commentContent, item.getCommentText(),
+                    HtmlCompat.FROM_HTML_MODE_LEGACY, getServiceById(item.getServiceId()),
+                    item.getUrl(), disposables, null);
+
+            return binding.getRoot();
+        };
+    }
+
 
     /*//////////////////////////////////////////////////////////////////////////
     // State saving
diff --git a/app/src/main/res/layout/comment_replies_header.xml b/app/src/main/res/layout/comment_replies_header.xml
new file mode 100644
index 000000000..ed5ba1a10
--- /dev/null
+++ b/app/src/main/res/layout/comment_replies_header.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.google.android.material.imageview.ShapeableImageView
+        android:id="@+id/authorAvatar"
+        android:layout_width="42dp"
+        android:layout_height="42dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:focusable="false"
+        android:src="@drawable/placeholder_person"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:shapeAppearance="@style/CircularImageView"
+        tools:ignore="RtlHardcoded" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/authorName"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:ellipsize="end"
+        android:lines="1"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textSize="16sp"
+        app:layout_constraintBottom_toTopOf="@+id/uploadDate"
+        app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
+        app:layout_constraintStart_toEndOf="@+id/authorAvatar"
+        app:layout_constraintTop_toTopOf="@+id/authorAvatar"
+        tools:text="@tools:sample/lorem/random" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/uploadDate"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:ellipsize="end"
+        android:lines="1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
+        app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
+        app:layout_constraintStart_toEndOf="@+id/authorAvatar"
+        app:layout_constraintTop_toBottomOf="@+id/authorName"
+        tools:text="5 months ago" />
+
+    <ImageView
+        android:id="@+id/thumbsUpImage"
+        android:layout_width="21sp"
+        android:layout_height="21sp"
+        android:layout_marginEnd="@dimen/video_item_detail_like_margin"
+        android:contentDescription="@string/detail_likes_img_view_description"
+        android:src="@drawable/ic_thumb_up"
+        app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
+        app:layout_constraintEnd_toStartOf="@+id/thumbsUpCount"
+        app:layout_constraintTop_toTopOf="@+id/authorAvatar" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/thumbsUpCount"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:gravity="center"
+        android:lines="1"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
+        app:layout_constraintEnd_toStartOf="@+id/heartImage"
+        app:layout_constraintTop_toTopOf="@+id/authorAvatar"
+        tools:text="12M" />
+
+    <ImageView
+        android:id="@+id/heartImage"
+        android:layout_width="21sp"
+        android:layout_height="21sp"
+        android:layout_marginEnd="4dp"
+        android:contentDescription="@string/detail_heart_img_view_description"
+        android:src="@drawable/ic_heart"
+        app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
+        app:layout_constraintEnd_toStartOf="@+id/pinnedImage"
+        app:layout_constraintTop_toTopOf="@+id/authorAvatar"
+        app:layout_goneMarginEnd="16dp" />
+
+    <View
+        android:id="@+id/authorTouchArea"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_margin="8dp"
+        android:background="?attr/selectableItemBackground"
+        app:layout_constraintBottom_toTopOf="@+id/commentContent"
+        app:layout_constraintEnd_toStartOf="@+id/thumbsUpImage"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <ImageView
+        android:id="@+id/pinnedImage"
+        android:layout_width="21sp"
+        android:layout_height="21sp"
+        android:layout_marginEnd="16dp"
+        android:contentDescription="@string/detail_pinned_comment_view_description"
+        android:src="@drawable/ic_pin"
+        app:layout_constraintBottom_toBottomOf="@+id/authorAvatar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/authorAvatar" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/commentContent"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="16dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textSize="14sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/authorAvatar"
+        tools:text="@tools:sample/lorem/random[10]" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1px"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="8dp"
+        android:background="?attr/separator_color"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/commentContent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file

From 3f37e27852a2d7559722bc69e7233ded68730ab1 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 15:18:26 +0200
Subject: [PATCH 054/141] Add some documentation and javadocs

Also further simplify CommentRepliesInfo and RelatedItemsInfo
---
 .../list/comments/CommentRepliesFragment.java |  4 +--
 .../list/comments/CommentRepliesInfo.java     | 25 ++++++++---------
 .../list/videos/RelatedItemInfo.java          | 28 -------------------
 .../list/videos/RelatedItemsFragment.java     | 22 +++++++--------
 .../list/videos/RelatedItemsInfo.java         | 22 +++++++++++++++
 .../holder/CommentInfoItemHolder.java         |  4 +--
 .../holder/StreamInfoItemHolder.java          |  5 ++--
 .../org/schabi/newpipe/util/Localization.java | 23 +++++++++++++--
 .../schabi/newpipe/util/NavigationHelper.java |  7 +++++
 .../schabi/newpipe/util/ServiceHelper.java    |  5 ++++
 10 files changed, 83 insertions(+), 62 deletions(-)
 delete mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
 create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsInfo.java

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 163e7d25f..2e0f0d7c6 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -89,7 +89,7 @@ public final class CommentRepliesFragment
             // setup author name and comment date
             binding.authorName.setText(item.getUploaderName());
             binding.uploadDate.setText(Localization.relativeTimeOrTextual(
-                    item.getUploadDate(), item.getTextualUploadDate(), getContext()));
+                    getContext(), item.getUploadDate(), item.getTextualUploadDate()));
             binding.authorTouchArea.setOnClickListener(
                     v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
 
@@ -140,7 +140,7 @@ public final class CommentRepliesFragment
 
     @Override
     protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
-        return Single.fromCallable(() -> CommentRepliesInfo.getInfo(commentsInfoItem,
+        return Single.fromCallable(() -> new CommentRepliesInfo(commentsInfoItem,
                 // the reply count string will be shown as the activity title
                 Localization.replyCount(requireContext(), commentsInfoItem.getReplyCount())));
     }
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
index 7f85dc6bf..cc160c395 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesInfo.java
@@ -7,19 +7,16 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
 import java.util.Collections;
 
 public final class CommentRepliesInfo extends ListInfo<CommentsInfoItem> {
-    private CommentRepliesInfo(final int serviceId,
-                               final ListLinkHandler listUrlIdHandler,
-                               final String name) {
-        super(serviceId, listUrlIdHandler, name);
-    }
-
-    public static CommentRepliesInfo getInfo(final CommentsInfoItem comment, final String name) {
-        final ListLinkHandler handler =
-                new ListLinkHandler("", "", "", Collections.emptyList(), null);
-        final CommentRepliesInfo relatedItemInfo = new CommentRepliesInfo(
-                comment.getServiceId(), handler, name); // the name will be shown as fragment title
-        relatedItemInfo.setNextPage(comment.getReplies());
-        relatedItemInfo.setRelatedItems(Collections.emptyList()); // since it must be non-null
-        return relatedItemInfo;
+    /**
+     * This class is used to wrap the comment replies page into a ListInfo object.
+     *
+     * @param comment the comment from which to get replies
+     * @param name will be shown as the fragment title
+     */
+    public CommentRepliesInfo(final CommentsInfoItem comment, final String name) {
+        super(comment.getServiceId(),
+                new ListLinkHandler("", "", "", Collections.emptyList(), null), name);
+        setNextPage(comment.getReplies());
+        setRelatedItems(Collections.emptyList()); // since it must be non-null
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
deleted file mode 100644
index 9d2985082..000000000
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemInfo.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.schabi.newpipe.fragments.list.videos;
-
-import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.ListInfo;
-import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
-import org.schabi.newpipe.extractor.stream.StreamInfo;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public final class RelatedItemInfo extends ListInfo<InfoItem> {
-    private RelatedItemInfo(final int serviceId,
-                            final ListLinkHandler listUrlIdHandler,
-                            final String name) {
-        super(serviceId, listUrlIdHandler, name);
-    }
-
-    public static RelatedItemInfo getInfo(final StreamInfo info) {
-        final ListLinkHandler handler = new ListLinkHandler(
-                info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
-        final RelatedItemInfo relatedItemInfo = new RelatedItemInfo(
-                info.getServiceId(), handler, info.getName());
-        final List<InfoItem> relatedItems = new ArrayList<>(info.getRelatedItems());
-        relatedItemInfo.setRelatedItems(relatedItems);
-        return relatedItemInfo;
-    }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
index 5ec316420..e46937ede 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
@@ -27,11 +27,11 @@ import java.util.function.Supplier;
 
 import io.reactivex.rxjava3.core.Single;
 
-public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, RelatedItemInfo>
+public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, RelatedItemsInfo>
         implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String INFO_KEY = "related_info_key";
 
-    private RelatedItemInfo relatedItemInfo;
+    private RelatedItemsInfo relatedItemsInfo;
 
     /*//////////////////////////////////////////////////////////////////////////
     // Views
@@ -68,7 +68,7 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
 
     @Override
     protected Supplier<View> getListHeaderSupplier() {
-        if (relatedItemInfo == null || relatedItemInfo.getRelatedItems() == null) {
+        if (relatedItemsInfo == null || relatedItemsInfo.getRelatedItems() == null) {
             return null;
         }
 
@@ -96,8 +96,8 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
     //////////////////////////////////////////////////////////////////////////*/
 
     @Override
-    protected Single<RelatedItemInfo> loadResult(final boolean forceLoad) {
-        return Single.fromCallable(() -> relatedItemInfo);
+    protected Single<RelatedItemsInfo> loadResult(final boolean forceLoad) {
+        return Single.fromCallable(() -> relatedItemsInfo);
     }
 
     @Override
@@ -109,7 +109,7 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
     }
 
     @Override
-    public void handleResult(@NonNull final RelatedItemInfo result) {
+    public void handleResult(@NonNull final RelatedItemsInfo result) {
         super.handleResult(result);
 
         if (headerBinding != null) {
@@ -136,23 +136,23 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
 
     private void setInitialData(final StreamInfo info) {
         super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
-        if (this.relatedItemInfo == null) {
-            this.relatedItemInfo = RelatedItemInfo.getInfo(info);
+        if (this.relatedItemsInfo == null) {
+            this.relatedItemsInfo = new RelatedItemsInfo(info);
         }
     }
 
     @Override
     public void onSaveInstanceState(@NonNull final Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putSerializable(INFO_KEY, relatedItemInfo);
+        outState.putSerializable(INFO_KEY, relatedItemsInfo);
     }
 
     @Override
     protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
         super.onRestoreInstanceState(savedState);
         final Serializable serializable = savedState.getSerializable(INFO_KEY);
-        if (serializable instanceof RelatedItemInfo) {
-            this.relatedItemInfo = (RelatedItemInfo) serializable;
+        if (serializable instanceof RelatedItemsInfo) {
+            this.relatedItemsInfo = (RelatedItemsInfo) serializable;
         }
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsInfo.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsInfo.java
new file mode 100644
index 000000000..bbc7e1ed0
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsInfo.java
@@ -0,0 +1,22 @@
+package org.schabi.newpipe.fragments.list.videos;
+
+import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.ListInfo;
+import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public final class RelatedItemsInfo extends ListInfo<InfoItem> {
+    /**
+     * This class is used to wrap the related items of a StreamInfo into a ListInfo object.
+     *
+     * @param info the stream info from which to get related items
+     */
+    public RelatedItemsInfo(final StreamInfo info) {
+        super(info.getServiceId(), new ListLinkHandler(info.getOriginalUrl(), info.getUrl(),
+                info.getId(), Collections.emptyList(), null), info.getName());
+        setRelatedItems(new ArrayList<>(info.getRelatedItems()));
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index 03f796f59..ef949a745 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -119,8 +119,8 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         // setup the top row, with pinned icon, author name and comment date
         itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
         itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(),
-                Localization.relativeTimeOrTextual(item.getUploadDate(),
-                        item.getTextualUploadDate(), itemBuilder.getContext())));
+                Localization.relativeTimeOrTextual(itemBuilder.getContext(), item.getUploadDate(),
+                        item.getTextualUploadDate())));
 
 
         // setup bottom row, with likes, heart and replies button
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
index 7f80707bc..80f62eed3 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java
@@ -77,8 +77,9 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
             }
         }
 
-        final String uploadDate = Localization.relativeTimeOrTextual(infoItem.getUploadDate(),
-                infoItem.getTextualUploadDate(), itemBuilder.getContext());
+        final String uploadDate = Localization.relativeTimeOrTextual(itemBuilder.getContext(),
+                infoItem.getUploadDate(),
+                infoItem.getTextualUploadDate());
         if (!TextUtils.isEmpty(uploadDate)) {
             if (viewsAndDate.isEmpty()) {
                 return uploadDate;
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 dea48651e..ef30e1761 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -217,6 +217,12 @@ public final class Localization {
                 String.valueOf(replyCount));
     }
 
+    /**
+     * @param context the Android context
+     * @param likeCount the like count, possibly negative if unknown
+     * @return if {@code likeCount} is smaller than {@code 0}, the string {@code "-"}, otherwise
+     *         the result of calling {@link #shortCount(Context, long)} on the like count
+     */
     public static String likeCount(final Context context, final int likeCount) {
         if (likeCount < 0) {
             return "-";
@@ -344,9 +350,20 @@ public final class Localization {
         return prettyTime.formatUnrounded(offsetDateTime);
     }
 
-    public static String relativeTimeOrTextual(final DateWrapper parsed,
-                                               final String textual,
-                                               @Nullable final Context context) {
+    /**
+     * @param context the Android context; if {@code null} then even if in debug mode and the
+     *                setting is enabled, {@code textual} will not be shown next to {@code parsed}
+     * @param parsed  the textual date or time ago parsed by NewPipeExtractor, or {@code null} if
+     *                the extractor could not parse it
+     * @param textual the original textual date or time ago string as provided by services
+     * @return {@link #relativeTime(OffsetDateTime)} is used if {@code parsed != null}, otherwise
+     *         {@code textual} is returned. If in debug mode, {@code context != null},
+     *         {@code parsed != null} and the relevant setting is enabled, {@code textual} will
+     *         be appended to the returned string for debugging purposes.
+     */
+    public static String relativeTimeOrTextual(@Nullable final Context context,
+                                               @Nullable final DateWrapper parsed,
+                                               final String textual) {
         if (parsed == null) {
             return textual;
         } else if (DEBUG && context != null && PreferenceManager
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index e127a8b55..e105e551b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -482,6 +482,13 @@ public final class NavigationHelper {
                 item.getServiceId(), uploaderUrl, item.getUploaderName());
     }
 
+    /**
+     * Opens the comment author channel fragment, if the {@link CommentsInfoItem#getUploaderUrl()}
+     * of {@code comment} is non-null. Shows a UI-error snackbar if something goes wrong.
+     *
+     * @param activity the activity with the fragment manager and in which to show the snackbar
+     * @param comment the comment whose uploader/author will be opened
+     */
     public static void openCommentAuthorIfPresent(@NonNull final FragmentActivity activity,
                                                   final CommentsInfoItem comment) {
         if (isEmpty(comment.getUploaderUrl())) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
index 68905e6c9..c712157b3 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -144,6 +144,11 @@ public final class ServiceHelper {
                 .orElse("<unknown>");
     }
 
+    /**
+     * @param serviceId the id of the service
+     * @return the service corresponding to the provided id
+     * @throws java.util.NoSuchElementException if there is no service with the provided id
+     */
     @NonNull
     public static StreamingService getServiceById(final int serviceId) {
         return ServiceList.all().stream()

From 8c9287d0c87dc2eb1e99f186535a5e2cb686c037 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 12 Apr 2023 15:44:50 +0200
Subject: [PATCH 055/141] Revert relying on source ListInfo, use
 commentsInfoItem.getUrl() instead

This reverts commit bb01da3691ff1d5c3dccd41b7ca1a5deb1b5676f. This commit was not needed
---
 .../fragments/list/BaseListInfoFragment.java  |  6 --
 .../list/comments/CommentRepliesFragment.java | 21 +++----
 .../newpipe/info_list/InfoItemBuilder.java    | 60 +++++++++++++++----
 .../newpipe/info_list/InfoListAdapter.java    |  5 --
 .../holder/CommentInfoItemHolder.java         |  3 +-
 .../schabi/newpipe/util/ExtractorHelper.java  |  9 +++
 .../schabi/newpipe/util/NavigationHelper.java |  5 +-
 7 files changed, 68 insertions(+), 41 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index 715512e02..dd5eb6c8a 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -66,7 +66,6 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
     @Override
     public void onResume() {
         super.onResume();
-
         // Check if it was loading when the fragment was stopped/paused,
         if (wasLoading.getAndSet(false)) {
             if (hasMoreItems() && !infoListAdapter.getItemsList().isEmpty()) {
@@ -75,8 +74,6 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
                 doInitialLoadLogic();
             }
         }
-
-        infoListAdapter.setSourceListInfo(currentInfo);
     }
 
     @Override
@@ -140,8 +137,6 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
         infoListAdapter.clearStreamItemList();
 
         currentInfo = null;
-        infoListAdapter.setSourceListInfo(null);
-
         if (currentWorker != null) {
             currentWorker.dispose();
         }
@@ -151,7 +146,6 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
                 .subscribe((@NonNull L result) -> {
                     isLoading.set(false);
                     currentInfo = result;
-                    infoListAdapter.setSourceListInfo(result);
                     currentNextPage = result.getNextPage();
                     handleResult(result);
                 }, throwable ->
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 2e0f0d7c6..a28a7f7d5 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -16,7 +16,6 @@ import org.schabi.newpipe.R;
 import org.schabi.newpipe.databinding.CommentRepliesHeaderBinding;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.ListExtractor;
-import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.info_list.ItemViewMode;
@@ -37,10 +36,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
 public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
 
-    // the original comments info loaded alongside the stream
-    private CommentsInfo commentsInfo;
-    // the comment to show replies of
-    private CommentsInfoItem commentsInfoItem;
+    private CommentsInfoItem commentsInfoItem; // the comment to show replies of
     private final CompositeDisposable disposables = new CompositeDisposable();
 
 
@@ -48,16 +44,16 @@ public final class CommentRepliesFragment
     // Constructors and lifecycle
     //////////////////////////////////////////////////////////////////////////*/
 
+    // only called by the Android framework, after which readFrom is called and restores all data
     public CommentRepliesFragment() {
         super(UserAction.REQUESTED_COMMENT_REPLIES);
     }
 
-    public CommentRepliesFragment(final CommentsInfo commentsInfo,
-                                  final CommentsInfoItem commentsInfoItem) {
+    public CommentRepliesFragment(final CommentsInfoItem commentsInfoItem) {
         this();
-        this.commentsInfo = commentsInfo;
         this.commentsInfoItem = commentsInfoItem;
-        setInitialData(commentsInfo.getServiceId(), commentsInfo.getUrl(), commentsInfo.getName());
+        // setting "" as title since the title will be properly set right after
+        setInitialData(commentsInfoItem.getServiceId(), commentsInfoItem.getUrl(), "");
     }
 
     @Nullable
@@ -122,14 +118,12 @@ public final class CommentRepliesFragment
     @Override
     public void writeTo(final Queue<Object> objectsToSave) {
         super.writeTo(objectsToSave);
-        objectsToSave.add(commentsInfo);
         objectsToSave.add(commentsInfoItem);
     }
 
     @Override
     public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
         super.readFrom(savedObjects);
-        commentsInfo = (CommentsInfo) savedObjects.poll();
         commentsInfoItem = (CommentsInfoItem) savedObjects.poll();
     }
 
@@ -147,7 +141,10 @@ public final class CommentRepliesFragment
 
     @Override
     protected Single<ListExtractor.InfoItemsPage<CommentsInfoItem>> loadMoreItemsLogic() {
-        return ExtractorHelper.getMoreCommentItems(serviceId, commentsInfo, currentNextPage);
+        // commentsInfoItem.getUrl() should contain the url of the original
+        // ListInfo<CommentsInfoItem>, which should be the stream url
+        return ExtractorHelper.getMoreCommentItems(
+                serviceId, commentsInfoItem.getUrl(), currentNextPage);
     }
 
 
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
index 3b66fa648..d959c6327 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -1,13 +1,25 @@
 package org.schabi.newpipe.info_list;
 
 import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
 
-import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.ListInfo;
+import androidx.annotation.NonNull;
+
+import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
+import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.InfoItemHolder;
+import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
+import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
+import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.OnClickGesture;
 
 /*
@@ -42,12 +54,44 @@ public class InfoItemBuilder {
     private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
     private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
 
-    private ListInfo<?> sourceListInfo; // the list-info the info-items from this list belong to
-
     public InfoItemBuilder(final Context context) {
         this.context = context;
     }
 
+    public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
+                          final HistoryRecordManager historyRecordManager) {
+        return buildView(parent, infoItem, historyRecordManager, false);
+    }
+
+    public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
+                          final HistoryRecordManager historyRecordManager,
+                          final boolean useMiniVariant) {
+        final InfoItemHolder holder =
+                holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
+        holder.updateFromItem(infoItem, historyRecordManager);
+        return holder.itemView;
+    }
+
+    private InfoItemHolder holderFromInfoType(@NonNull final ViewGroup parent,
+                                              @NonNull final InfoItem.InfoType infoType,
+                                              final boolean useMiniVariant) {
+        switch (infoType) {
+            case STREAM:
+                return useMiniVariant ? new StreamMiniInfoItemHolder(this, parent)
+                        : new StreamInfoItemHolder(this, parent);
+            case CHANNEL:
+                return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent)
+                        : new ChannelInfoItemHolder(this, parent);
+            case PLAYLIST:
+                return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
+                        : new PlaylistInfoItemHolder(this, parent);
+            case COMMENT:
+                return new CommentInfoItemHolder(this, parent);
+            default:
+                throw new RuntimeException("InfoType not expected = " + infoType.name());
+        }
+    }
+
     public Context getContext() {
         return context;
     }
@@ -84,12 +128,4 @@ public class InfoItemBuilder {
             final OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
         this.onCommentsSelectedListener = onCommentsSelectedListener;
     }
-
-    public Info getSourceListInfo() {
-        return sourceListInfo;
-    }
-
-    public void setSourceListInfo(final ListInfo<?> sourceListInfo) {
-        this.sourceListInfo = sourceListInfo;
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index 902c4665c..575568c00 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -13,7 +13,6 @@ import androidx.recyclerview.widget.RecyclerView;
 
 import org.schabi.newpipe.databinding.PignateFooterBinding;
 import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.ListInfo;
 import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
@@ -116,10 +115,6 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
         infoItemBuilder.setOnCommentsSelectedListener(listener);
     }
 
-    public void setSourceListInfo(final ListInfo<?> sourceInfo) {
-        infoItemBuilder.setSourceListInfo(sourceInfo);
-    }
-
     public void setUseMiniVariant(final boolean useMiniVariant) {
         this.useMiniVariant = useMiniVariant;
     }
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index ef949a745..ec336f677 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -21,7 +21,6 @@ import androidx.fragment.app.FragmentActivity;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.stream.Description;
 import org.schabi.newpipe.info_list.InfoItemBuilder;
@@ -174,7 +173,7 @@ public class CommentInfoItemHolder extends InfoItemHolder {
 
     private void openCommentReplies(final CommentsInfoItem item) {
         NavigationHelper.openCommentRepliesFragment((FragmentActivity) itemBuilder.getContext(),
-                (CommentsInfo) itemBuilder.getSourceListInfo(), item);
+                item);
     }
 
     private void allowLinkFocus() {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 07d0f516d..c2748f725 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -162,6 +162,15 @@ public final class ExtractorHelper {
                 CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPage));
     }
 
+    public static Single<InfoItemsPage<CommentsInfoItem>> getMoreCommentItems(
+            final int serviceId,
+            final String url,
+            final Page nextPage) {
+        checkServiceId(serviceId);
+        return Single.fromCallable(() ->
+                CommentsInfo.getMoreItems(NewPipe.getService(serviceId), url, nextPage));
+    }
+
     public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId,
                                                        final String url,
                                                        final boolean forceLoad) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index e105e551b..00af400b6 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -34,7 +34,6 @@ import org.schabi.newpipe.download.DownloadActivity;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.comments.CommentsInfo;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.AudioStream;
@@ -503,11 +502,9 @@ public final class NavigationHelper {
     }
 
     public static void openCommentRepliesFragment(@NonNull final FragmentActivity activity,
-                                                  final CommentsInfo commentsInfo,
                                                   final CommentsInfoItem commentsInfoItem) {
         defaultTransaction(activity.getSupportFragmentManager())
-                .replace(R.id.fragment_holder,
-                        new CommentRepliesFragment(commentsInfo, commentsInfoItem))
+                .replace(R.id.fragment_holder, new CommentRepliesFragment(commentsInfoItem))
                 .addToBackStack(null)
                 .commit();
     }

From 5f32d001ccad138faad1c8182be244093da1f8a3 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Tue, 29 Aug 2023 20:51:47 +0200
Subject: [PATCH 056/141] Expand DetailFragment again when exiting the
 CommentRepliesFragment

---
 .../java/org/schabi/newpipe/MainActivity.java | 38 ++++++++++++++++---
 .../list/comments/CommentRepliesFragment.java |  3 ++
 .../schabi/newpipe/util/NavigationHelper.java |  2 +-
 3 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index ea047ce61..300697697 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -69,6 +69,7 @@ import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
 import org.schabi.newpipe.fragments.BackPressable;
 import org.schabi.newpipe.fragments.MainFragment;
 import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
+import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
 import org.schabi.newpipe.fragments.list.search.SearchFragment;
 import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
 import org.schabi.newpipe.player.Player;
@@ -546,14 +547,26 @@ public class MainActivity extends AppCompatActivity {
         // interacts with a fragment inside fragment_holder so all back presses should be
         // handled by it
         if (bottomSheetHiddenOrCollapsed()) {
-            final Fragment fragment = getSupportFragmentManager()
-                    .findFragmentById(R.id.fragment_holder);
+            final FragmentManager fm = getSupportFragmentManager();
+            final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
             // If current fragment implements BackPressable (i.e. can/wanna handle back press)
             // delegate the back press to it
             if (fragment instanceof BackPressable) {
                 if (((BackPressable) fragment).onBackPressed()) {
                     return;
                 }
+            } else if (fragment instanceof CommentRepliesFragment) {
+                // expand DetailsFragment if CommentRepliesFragment was opened
+                // to show the top level comments again
+                // Expand DetailsFragment if CommentRepliesFragment was opened
+                // and no other CommentRepliesFragments are on top of the back stack
+                // to show the top level comments again.
+                final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
+                        fm.getBackStackEntryCount() - 2); // current fragment is at the top
+                if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
+                    BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
+                            .setState(BottomSheetBehavior.STATE_EXPANDED);
+                }
             }
 
         } else {
@@ -629,10 +642,23 @@ public class MainActivity extends AppCompatActivity {
      * </pre>
      */
     private void onHomeButtonPressed() {
-        // If search fragment wasn't found in the backstack...
-        if (!NavigationHelper.tryGotoSearchFragment(getSupportFragmentManager())) {
-            // ...go to the main fragment
-            NavigationHelper.gotoMainFragment(getSupportFragmentManager());
+        final FragmentManager fm = getSupportFragmentManager();
+        final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
+
+        if (fragment instanceof CommentRepliesFragment) {
+            // Expand DetailsFragment if CommentRepliesFragment was opened
+            // and no other CommentRepliesFragments are on top of the back stack
+            // to show the top level comments again.
+            fm.popBackStackImmediate();
+            final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
+                    fm.getBackStackEntryCount() - 1);
+            if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
+                BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
+                        .setState(BottomSheetBehavior.STATE_EXPANDED);
+            }
+        } else if (!NavigationHelper.tryGotoSearchFragment(fm)) {
+            // If search fragment wasn't found in the backstack go to the main fragment
+            NavigationHelper.gotoMainFragment(fm);
         }
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index a28a7f7d5..b8d0f1e6c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -36,6 +36,8 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
 public final class CommentRepliesFragment
         extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
 
+    public static final String TAG = CommentRepliesFragment.class.getSimpleName();
+
     private CommentsInfoItem commentsInfoItem; // the comment to show replies of
     private final CompositeDisposable disposables = new CompositeDisposable();
 
@@ -156,4 +158,5 @@ public final class CommentRepliesFragment
     protected ItemViewMode getItemViewMode() {
         return ItemViewMode.LIST;
     }
+
 }
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 00af400b6..d0da22739 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -505,7 +505,7 @@ public final class NavigationHelper {
                                                   final CommentsInfoItem commentsInfoItem) {
         defaultTransaction(activity.getSupportFragmentManager())
                 .replace(R.id.fragment_holder, new CommentRepliesFragment(commentsInfoItem))
-                .addToBackStack(null)
+                .addToBackStack(CommentRepliesFragment.TAG)
                 .commit();
     }
 

From b4016c91c11c4479263aa1e4ccd102c6fb23b7f4 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Thu, 31 Aug 2023 13:42:07 +0200
Subject: [PATCH 057/141] scroll last comment into view

---
 .../java/org/schabi/newpipe/MainActivity.java | 46 +++++++++++++++----
 .../fragments/detail/VideoDetailFragment.java |  9 ++++
 .../list/comments/CommentRepliesFragment.java |  6 +++
 .../list/comments/CommentsFragment.java       |  4 ++
 .../schabi/newpipe/util/NavigationHelper.java |  3 +-
 5 files changed, 59 insertions(+), 9 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 300697697..5c56e867c 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -563,10 +563,7 @@ public class MainActivity extends AppCompatActivity {
                 // to show the top level comments again.
                 final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
                         fm.getBackStackEntryCount() - 2); // current fragment is at the top
-                if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
-                    BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
-                            .setState(BottomSheetBehavior.STATE_EXPANDED);
-                }
+                openDetailFragmentFromCommentReplies(fm, bse);
             }
 
         } else {
@@ -652,10 +649,7 @@ public class MainActivity extends AppCompatActivity {
             fm.popBackStackImmediate();
             final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
                     fm.getBackStackEntryCount() - 1);
-            if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
-                BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
-                        .setState(BottomSheetBehavior.STATE_EXPANDED);
-            }
+            openDetailFragmentFromCommentReplies(fm, bse);
         } else if (!NavigationHelper.tryGotoSearchFragment(fm)) {
             // If search fragment wasn't found in the backstack go to the main fragment
             NavigationHelper.gotoMainFragment(fm);
@@ -854,6 +848,42 @@ public class MainActivity extends AppCompatActivity {
         }
     }
 
+    private void openDetailFragmentFromCommentReplies(
+            @NonNull final FragmentManager fm,
+            @NonNull final FragmentManager.BackStackEntry bse) {
+        if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
+            final CommentRepliesFragment commentRepliesFragment =
+                    (CommentRepliesFragment) fm.findFragmentByTag(
+                            CommentRepliesFragment.TAG);
+            final BottomSheetBehavior bsb = BottomSheetBehavior
+                    .from(mainBinding.fragmentPlayerHolder);
+            bsb.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+                @Override
+                public void onStateChanged(@NonNull final View bottomSheet,
+                                           final int newState) {
+                    if (newState == BottomSheetBehavior.STATE_EXPANDED) {
+                        final Fragment detailFragment = fm.findFragmentById(
+                                R.id.fragment_player_holder);
+                        if (detailFragment instanceof VideoDetailFragment
+                                && commentRepliesFragment != null) {
+                            // should always be the case
+                            ((VideoDetailFragment) detailFragment).scrollToComment(
+                                    commentRepliesFragment.getCommentsInfoItem());
+                        }
+                        bsb.removeBottomSheetCallback(this);
+                    }
+                }
+
+                @Override
+                public void onSlide(@NonNull final View bottomSheet,
+                                    final float slideOffset) {
+                    // not needed, listener is removed once the sheet is expanded
+                }
+            });
+            bsb.setState(BottomSheetBehavior.STATE_EXPANDED);
+        }
+    }
+
     private boolean bottomSheetHiddenOrCollapsed() {
         final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
                 BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder);
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 7db5e0251..d607c5401 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -74,6 +74,7 @@ import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.Image;
 import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
 import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.AudioStream;
@@ -1012,6 +1013,14 @@ public final class VideoDetailFragment
         updateTabLayoutVisibility();
     }
 
+    public void scrollToComment(final CommentsInfoItem comment) {
+        final Fragment fragment = pageAdapter.getItem(
+                pageAdapter.getItemPositionByTitle(COMMENTS_TAB_TAG));
+        if (fragment instanceof CommentsFragment) {
+            ((CommentsFragment) fragment).scrollToComment(comment);
+        }
+    }
+
     /*//////////////////////////////////////////////////////////////////////////
     // Play Utils
     //////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index b8d0f1e6c..62f97c727 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -159,4 +159,10 @@ public final class CommentRepliesFragment
         return ItemViewMode.LIST;
     }
 
+    /**
+     * @return the comment to which the replies are shown
+     */
+    public CommentsInfoItem getCommentsInfoItem() {
+        return commentsInfoItem;
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
index 5a5f84968..9481eba81 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
@@ -110,4 +110,8 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfoItem, Com
     protected ItemViewMode getItemViewMode() {
         return ItemViewMode.LIST;
     }
+
+    public void scrollToComment(final CommentsInfoItem comment) {
+        itemsList.scrollToPosition(infoListAdapter.getItemsList().indexOf(comment));
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index d0da22739..0fcea5dd2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -504,7 +504,8 @@ public final class NavigationHelper {
     public static void openCommentRepliesFragment(@NonNull final FragmentActivity activity,
                                                   final CommentsInfoItem commentsInfoItem) {
         defaultTransaction(activity.getSupportFragmentManager())
-                .replace(R.id.fragment_holder, new CommentRepliesFragment(commentsInfoItem))
+                .replace(R.id.fragment_holder, new CommentRepliesFragment(commentsInfoItem),
+                        CommentRepliesFragment.TAG)
                 .addToBackStack(CommentRepliesFragment.TAG)
                 .commit();
     }

From d76e9b0bd8940f8afc038f0b1d59581d2f0324fd Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 22 Dec 2023 12:50:32 +0100
Subject: [PATCH 058/141] Fix scrolling to correct comment after closing
 replies

---
 .../java/org/schabi/newpipe/MainActivity.java | 100 +++++++++++-------
 .../fragments/detail/VideoDetailFragment.java |  14 ++-
 .../list/comments/CommentsFragment.java       |  10 +-
 3 files changed, 80 insertions(+), 44 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 5c56e867c..a096e7eaf 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -44,6 +44,7 @@ import android.widget.FrameLayout;
 import android.widget.Spinner;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.ActionBarDrawerToggle;
 import androidx.appcompat.app.AppCompatActivity;
@@ -51,6 +52,7 @@ import androidx.core.app.ActivityCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentContainerView;
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.PreferenceManager;
 
@@ -64,6 +66,7 @@ import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
 import org.schabi.newpipe.fragments.BackPressable;
@@ -561,9 +564,7 @@ public class MainActivity extends AppCompatActivity {
                 // Expand DetailsFragment if CommentRepliesFragment was opened
                 // and no other CommentRepliesFragments are on top of the back stack
                 // to show the top level comments again.
-                final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
-                        fm.getBackStackEntryCount() - 2); // current fragment is at the top
-                openDetailFragmentFromCommentReplies(fm, bse);
+                openDetailFragmentFromCommentReplies(fm, false);
             }
 
         } else {
@@ -646,10 +647,7 @@ public class MainActivity extends AppCompatActivity {
             // Expand DetailsFragment if CommentRepliesFragment was opened
             // and no other CommentRepliesFragments are on top of the back stack
             // to show the top level comments again.
-            fm.popBackStackImmediate();
-            final FragmentManager.BackStackEntry bse = fm.getBackStackEntryAt(
-                    fm.getBackStackEntryCount() - 1);
-            openDetailFragmentFromCommentReplies(fm, bse);
+            openDetailFragmentFromCommentReplies(fm, true);
         } else if (!NavigationHelper.tryGotoSearchFragment(fm)) {
             // If search fragment wasn't found in the backstack go to the main fragment
             NavigationHelper.gotoMainFragment(fm);
@@ -850,38 +848,64 @@ public class MainActivity extends AppCompatActivity {
 
     private void openDetailFragmentFromCommentReplies(
             @NonNull final FragmentManager fm,
-            @NonNull final FragmentManager.BackStackEntry bse) {
-        if (!CommentRepliesFragment.TAG.equals(bse.getName())) {
-            final CommentRepliesFragment commentRepliesFragment =
-                    (CommentRepliesFragment) fm.findFragmentByTag(
-                            CommentRepliesFragment.TAG);
-            final BottomSheetBehavior bsb = BottomSheetBehavior
-                    .from(mainBinding.fragmentPlayerHolder);
-            bsb.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
-                @Override
-                public void onStateChanged(@NonNull final View bottomSheet,
-                                           final int newState) {
-                    if (newState == BottomSheetBehavior.STATE_EXPANDED) {
-                        final Fragment detailFragment = fm.findFragmentById(
-                                R.id.fragment_player_holder);
-                        if (detailFragment instanceof VideoDetailFragment
-                                && commentRepliesFragment != null) {
-                            // should always be the case
-                            ((VideoDetailFragment) detailFragment).scrollToComment(
-                                    commentRepliesFragment.getCommentsInfoItem());
-                        }
-                        bsb.removeBottomSheetCallback(this);
-                    }
-                }
-
-                @Override
-                public void onSlide(@NonNull final View bottomSheet,
-                                    final float slideOffset) {
-                    // not needed, listener is removed once the sheet is expanded
-                }
-            });
-            bsb.setState(BottomSheetBehavior.STATE_EXPANDED);
+            final boolean popBackStack
+    ) {
+        // obtain the name of the fragment under the replies fragment that's going to be popped
+        @Nullable final String fragmentUnderEntryName;
+        if (fm.getBackStackEntryCount() < 2) {
+            fragmentUnderEntryName = null;
+        } else {
+            fragmentUnderEntryName = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 2)
+                    .getName();
         }
+
+        // the root comment is the comment for which the user opened the replies page
+        @Nullable final CommentRepliesFragment repliesFragment =
+                (CommentRepliesFragment) fm.findFragmentByTag(CommentRepliesFragment.TAG);
+        @Nullable final CommentsInfoItem rootComment =
+                repliesFragment == null ? null : repliesFragment.getCommentsInfoItem();
+
+        // sometimes this function pops the backstack, other times it's handled by the system
+        if (popBackStack) {
+            fm.popBackStackImmediate();
+        }
+
+        // only expand the bottom sheet back if there are no more nested comment replies fragments
+        // stacked under the one that is currently being popped
+        if (CommentRepliesFragment.TAG.equals(fragmentUnderEntryName)) {
+            return;
+        }
+
+        final BottomSheetBehavior<FragmentContainerView> behavior = BottomSheetBehavior
+                .from(mainBinding.fragmentPlayerHolder);
+        // do not return to the comment if the details fragment was closed
+        if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
+            return;
+        }
+
+        // scroll to the root comment once the bottom sheet expansion animation is finished
+        behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+            @Override
+            public void onStateChanged(@NonNull final View bottomSheet,
+                                       final int newState) {
+                if (newState == BottomSheetBehavior.STATE_EXPANDED) {
+                    final Fragment detailFragment = fm.findFragmentById(
+                            R.id.fragment_player_holder);
+                    if (detailFragment instanceof VideoDetailFragment && rootComment != null) {
+                        // should always be the case
+                        ((VideoDetailFragment) detailFragment).scrollToComment(rootComment);
+                    }
+                    behavior.removeBottomSheetCallback(this);
+                }
+            }
+
+            @Override
+            public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
+                // not needed, listener is removed once the sheet is expanded
+            }
+        });
+
+        behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
     }
 
     private boolean bottomSheetHiddenOrCollapsed() {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index d607c5401..4da0a561e 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -1014,10 +1014,16 @@ public final class VideoDetailFragment
     }
 
     public void scrollToComment(final CommentsInfoItem comment) {
-        final Fragment fragment = pageAdapter.getItem(
-                pageAdapter.getItemPositionByTitle(COMMENTS_TAB_TAG));
-        if (fragment instanceof CommentsFragment) {
-            ((CommentsFragment) fragment).scrollToComment(comment);
+        final int commentsTabPos = pageAdapter.getItemPositionByTitle(COMMENTS_TAB_TAG);
+        final Fragment fragment = pageAdapter.getItem(commentsTabPos);
+        if (!(fragment instanceof CommentsFragment)) {
+            return;
+        }
+
+        // unexpand the app bar only if scrolling to the comment succeeded
+        if (((CommentsFragment) fragment).scrollToComment(comment)) {
+            binding.appBarLayout.setExpanded(false, false);
+            binding.viewPager.setCurrentItem(commentsTabPos, false);
         }
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
index 9481eba81..e25e02794 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java
@@ -111,7 +111,13 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfoItem, Com
         return ItemViewMode.LIST;
     }
 
-    public void scrollToComment(final CommentsInfoItem comment) {
-        itemsList.scrollToPosition(infoListAdapter.getItemsList().indexOf(comment));
+    public boolean scrollToComment(final CommentsInfoItem comment) {
+        final int position = infoListAdapter.getItemsList().indexOf(comment);
+        if (position < 0) {
+            return false;
+        }
+
+        itemsList.scrollToPosition(position);
+        return true;
     }
 }

From aa84d6fc8febd9b442fe26a5ac5b206ce8212b62 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 22 Dec 2023 18:50:48 +0100
Subject: [PATCH 059/141] Add @NonNull annotations

---
 .../list/comments/CommentRepliesFragment.java |  2 +-
 .../holder/CommentInfoItemHolder.java         |  5 +-
 .../org/schabi/newpipe/util/Localization.java | 74 +++++++++++--------
 .../schabi/newpipe/util/NavigationHelper.java |  6 +-
 4 files changed, 50 insertions(+), 37 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index 62f97c727..a816b149f 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -51,7 +51,7 @@ public final class CommentRepliesFragment
         super(UserAction.REQUESTED_COMMENT_REPLIES);
     }
 
-    public CommentRepliesFragment(final CommentsInfoItem commentsInfoItem) {
+    public CommentRepliesFragment(@NonNull final CommentsInfoItem commentsInfoItem) {
         this();
         this.commentsInfoItem = commentsInfoItem;
         // setting "" as title since the title will be properly set right after
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index ec336f677..8327b398b 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -14,6 +14,7 @@ import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.text.HtmlCompat;
 import androidx.fragment.app.FragmentActivity;
@@ -166,12 +167,12 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         });
     }
 
-    private void openCommentAuthor(final CommentsInfoItem item) {
+    private void openCommentAuthor(@NonNull final CommentsInfoItem item) {
         NavigationHelper.openCommentAuthorIfPresent((FragmentActivity) itemBuilder.getContext(),
                 item);
     }
 
-    private void openCommentReplies(final CommentsInfoItem item) {
+    private void openCommentReplies(@NonNull final CommentsInfoItem item) {
         NavigationHelper.openCommentRepliesFragment((FragmentActivity) itemBuilder.getContext(),
                 item);
     }
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 ef30e1761..0485413cc 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -85,7 +85,7 @@ public final class Localization {
                 .fromLocale(getPreferredLocale(context));
     }
 
-    public static ContentCountry getPreferredContentCountry(final Context context) {
+    public static ContentCountry getPreferredContentCountry(@NonNull final Context context) {
         final String contentCountry = PreferenceManager.getDefaultSharedPreferences(context)
                 .getString(context.getString(R.string.content_country_key),
                         context.getString(R.string.default_localization_key));
@@ -95,41 +95,43 @@ public final class Localization {
         return new ContentCountry(contentCountry);
     }
 
-    public static Locale getPreferredLocale(final Context context) {
+    public static Locale getPreferredLocale(@NonNull final Context context) {
         return getLocaleFromPrefs(context, R.string.content_language_key);
     }
 
-    public static Locale getAppLocale(final Context context) {
+    public static Locale getAppLocale(@NonNull final Context context) {
         return getLocaleFromPrefs(context, R.string.app_language_key);
     }
 
-    public static String localizeNumber(final Context context, final long number) {
+    public static String localizeNumber(@NonNull final Context context, final long number) {
         return localizeNumber(context, (double) number);
     }
 
-    public static String localizeNumber(final Context context, final double number) {
+    public static String localizeNumber(@NonNull final Context context, final double number) {
         final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context));
         return nf.format(number);
     }
 
-    public static String formatDate(final OffsetDateTime offsetDateTime, final Context context) {
+    public static String formatDate(@NonNull final Context context,
+                                    @NonNull final OffsetDateTime offsetDateTime) {
         return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
                 .withLocale(getAppLocale(context)).format(offsetDateTime
                         .atZoneSameInstant(ZoneId.systemDefault()));
     }
 
     @SuppressLint("StringFormatInvalid")
-    public static String localizeUploadDate(final Context context,
-                                            final OffsetDateTime offsetDateTime) {
-        return context.getString(R.string.upload_date_text, formatDate(offsetDateTime, context));
+    public static String localizeUploadDate(@NonNull final Context context,
+                                            @NonNull final OffsetDateTime offsetDateTime) {
+        return context.getString(R.string.upload_date_text, formatDate(context, offsetDateTime));
     }
 
-    public static String localizeViewCount(final Context context, final long viewCount) {
+    public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
         return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
                 localizeNumber(context, viewCount));
     }
 
-    public static String localizeStreamCount(final Context context, final long streamCount) {
+    public static String localizeStreamCount(@NonNull final Context context,
+                                             final long streamCount) {
         switch ((int) streamCount) {
             case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
                 return "";
@@ -143,7 +145,8 @@ public final class Localization {
         }
     }
 
-    public static String localizeStreamCountMini(final Context context, final long streamCount) {
+    public static String localizeStreamCountMini(@NonNull final Context context,
+                                                 final long streamCount) {
         switch ((int) streamCount) {
             case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
                 return "";
@@ -156,12 +159,13 @@ public final class Localization {
         }
     }
 
-    public static String localizeWatchingCount(final Context context, final long watchingCount) {
+    public static String localizeWatchingCount(@NonNull final Context context,
+                                               final long watchingCount) {
         return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
                 localizeNumber(context, watchingCount));
     }
 
-    public static String shortCount(final Context context, final long count) {
+    public static String shortCount(@NonNull final Context context, final long count) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             return CompactDecimalFormat.getInstance(getAppLocale(context),
                     CompactDecimalFormat.CompactStyle.SHORT).format(count);
@@ -182,37 +186,40 @@ public final class Localization {
         }
     }
 
-    public static String listeningCount(final Context context, final long listeningCount) {
+    public static String listeningCount(@NonNull final Context context, final long listeningCount) {
         return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount,
                 shortCount(context, listeningCount));
     }
 
-    public static String shortWatchingCount(final Context context, final long watchingCount) {
+    public static String shortWatchingCount(@NonNull final Context context,
+                                            final long watchingCount) {
         return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
                 shortCount(context, watchingCount));
     }
 
-    public static String shortViewCount(final Context context, final long viewCount) {
+    public static String shortViewCount(@NonNull final Context context, final long viewCount) {
         return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
                 shortCount(context, viewCount));
     }
 
-    public static String shortSubscriberCount(final Context context, final long subscriberCount) {
+    public static String shortSubscriberCount(@NonNull final Context context,
+                                              final long subscriberCount) {
         return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount,
                 shortCount(context, subscriberCount));
     }
 
-    public static String downloadCount(final Context context, final int downloadCount) {
+    public static String downloadCount(@NonNull final Context context, final int downloadCount) {
         return getQuantity(context, R.plurals.download_finished_notification, 0,
                 downloadCount, shortCount(context, downloadCount));
     }
 
-    public static String deletedDownloadCount(final Context context, final int deletedCount) {
+    public static String deletedDownloadCount(@NonNull final Context context,
+                                              final int deletedCount) {
         return getQuantity(context, R.plurals.deleted_downloads_toast, 0,
                 deletedCount, shortCount(context, deletedCount));
     }
 
-    public static String replyCount(final Context context, final int replyCount) {
+    public static String replyCount(@NonNull final Context context, final int replyCount) {
         return getQuantity(context, R.plurals.replies, 0, replyCount,
                 String.valueOf(replyCount));
     }
@@ -223,7 +230,7 @@ public final class Localization {
      * @return if {@code likeCount} is smaller than {@code 0}, the string {@code "-"}, otherwise
      *         the result of calling {@link #shortCount(Context, long)} on the like count
      */
-    public static String likeCount(final Context context, final int likeCount) {
+    public static String likeCount(@NonNull final Context context, final int likeCount) {
         if (likeCount < 0) {
             return "-";
         } else {
@@ -263,7 +270,8 @@ public final class Localization {
      * @return duration in a human readable string.
      */
     @NonNull
-    public static String localizeDuration(final Context context, final int durationInSecs) {
+    public static String localizeDuration(@NonNull final Context context,
+                                          final int durationInSecs) {
         if (durationInSecs < 0) {
             throw new IllegalArgumentException("duration can not be negative");
         }
@@ -300,7 +308,7 @@ public final class Localization {
      * @param track   an {@link AudioStream} of the track
      * @return the localized name of the audio track
      */
-    public static String audioTrackName(final Context context, final AudioStream track) {
+    public static String audioTrackName(@NonNull final Context context, final AudioStream track) {
         final String name;
         if (track.getAudioLocale() != null) {
             name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context));
@@ -320,7 +328,8 @@ public final class Localization {
     }
 
     @Nullable
-    private static String audioTrackType(final Context context, final AudioTrackType trackType) {
+    private static String audioTrackType(@NonNull final Context context,
+                                         final AudioTrackType trackType) {
         switch (trackType) {
             case ORIGINAL:
                 return context.getString(R.string.audio_track_type_original);
@@ -336,17 +345,17 @@ public final class Localization {
     // Pretty Time
     //////////////////////////////////////////////////////////////////////////*/
 
-    public static void initPrettyTime(final PrettyTime time) {
+    public static void initPrettyTime(@NonNull final PrettyTime time) {
         prettyTime = time;
         // Do not use decades as YouTube doesn't either.
         prettyTime.removeUnit(Decade.class);
     }
 
-    public static PrettyTime resolvePrettyTime(final Context context) {
+    public static PrettyTime resolvePrettyTime(@NonNull final Context context) {
         return new PrettyTime(getAppLocale(context));
     }
 
-    public static String relativeTime(final OffsetDateTime offsetDateTime) {
+    public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) {
         return prettyTime.formatUnrounded(offsetDateTime);
     }
 
@@ -383,7 +392,8 @@ public final class Localization {
         res.updateConfiguration(conf, dm);
     }
 
-    private static Locale getLocaleFromPrefs(final Context context, @StringRes final int prefKey) {
+    private static Locale getLocaleFromPrefs(@NonNull final Context context,
+                                             @StringRes final int prefKey) {
         final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
         final String defaultKey = context.getString(R.string.default_localization_key);
         final String languageCode = sp.getString(context.getString(prefKey), defaultKey);
@@ -399,8 +409,10 @@ public final class Localization {
         return new BigDecimal(value).setScale(1, RoundingMode.HALF_UP).doubleValue();
     }
 
-    private static String getQuantity(final Context context, @PluralsRes final int pluralId,
-                                      @StringRes final int zeroCaseStringId, final long count,
+    private static String getQuantity(@NonNull final Context context,
+                                      @PluralsRes final int pluralId,
+                                      @StringRes final int zeroCaseStringId,
+                                      final long count,
                                       final String formattedCount) {
         if (count == 0) {
             return context.getString(zeroCaseStringId);
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 0fcea5dd2..5dee32371 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -489,7 +489,7 @@ public final class NavigationHelper {
      * @param comment the comment whose uploader/author will be opened
      */
     public static void openCommentAuthorIfPresent(@NonNull final FragmentActivity activity,
-                                                  final CommentsInfoItem comment) {
+                                                  @NonNull final CommentsInfoItem comment) {
         if (isEmpty(comment.getUploaderUrl())) {
             return;
         }
@@ -502,9 +502,9 @@ public final class NavigationHelper {
     }
 
     public static void openCommentRepliesFragment(@NonNull final FragmentActivity activity,
-                                                  final CommentsInfoItem commentsInfoItem) {
+                                                  @NonNull final CommentsInfoItem comment) {
         defaultTransaction(activity.getSupportFragmentManager())
-                .replace(R.id.fragment_holder, new CommentRepliesFragment(commentsInfoItem),
+                .replace(R.id.fragment_holder, new CommentRepliesFragment(comment),
                         CommentRepliesFragment.TAG)
                 .addToBackStack(CommentRepliesFragment.TAG)
                 .commit();

From e4a07411b8a591b4f0a15ef056f796cf519354a1 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 16:25:19 -0400
Subject: [PATCH 060/141] Update some AndroidX libraries

---
 app/build.gradle | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 993363e32..131bcaf0f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -106,9 +106,9 @@ android {
 ext {
     checkstyleVersion = '10.12.1'
 
-    androidxLifecycleVersion = '2.5.1'
+    androidxLifecycleVersion = '2.6.2'
     androidxRoomVersion = '2.5.2'
-    androidxWorkVersion = '2.7.1'
+    androidxWorkVersion = '2.8.1'
 
     icepickVersion = '3.2.0'
     exoPlayerVersion = '2.18.7'
@@ -208,25 +208,25 @@ dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
 
 /** AndroidX **/
-    implementation 'androidx.appcompat:appcompat:1.5.1'
+    implementation 'androidx.appcompat:appcompat:1.6.1'
     implementation 'androidx.cardview:cardview:1.0.0'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
-    implementation 'androidx.core:core-ktx:1.10.0'
+    implementation 'androidx.core:core-ktx:1.12.0'
     implementation 'androidx.documentfile:documentfile:1.0.1'
-    implementation 'androidx.fragment:fragment-ktx:1.4.1'
+    implementation 'androidx.fragment:fragment-ktx:1.6.1'
     implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
     implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
     implementation 'androidx.media:media:1.6.0'
-    implementation 'androidx.preference:preference:1.2.0'
-    implementation 'androidx.recyclerview:recyclerview:1.2.1'
+    implementation 'androidx.preference:preference:1.2.1'
+    implementation 'androidx.recyclerview:recyclerview:1.3.2'
     implementation "androidx.room:room-runtime:${androidxRoomVersion}"
     implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
     kapt "androidx.room:room-compiler:${androidxRoomVersion}"
     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
     // Newer version specified to prevent accessibility regressions with RecyclerView, see:
     // 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-beta02'
     implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
     implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
     implementation 'com.google.android.material:material:1.9.0'

From 00566ed4d442bda54533459fbded06d91be230a3 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 16:26:20 -0400
Subject: [PATCH 061/141] Fix AndroidX Work deprecation

---
 .../newpipe/local/feed/notifications/NotificationWorker.kt      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt
index de640dbbb..a40bf35dc 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt
@@ -137,7 +137,7 @@ class NotificationWorker(
                 .enqueueUniquePeriodicWork(
                     WORK_TAG,
                     if (force) {
-                        ExistingPeriodicWorkPolicy.REPLACE
+                        ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
                     } else {
                         ExistingPeriodicWorkPolicy.KEEP
                     },

From f3b458c803eb1f565d5d16913d37e26cd95992fc Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <31027858+isira-seneviratne@users.noreply.github.com>
Date: Tue, 31 Oct 2023 16:31:16 -0400
Subject: [PATCH 062/141] Bump compileSdk to 34

---
 app/build.gradle                                           | 2 +-
 .../newpipe/player/gesture/MainPlayerGestureListener.kt    | 5 ++---
 .../newpipe/player/gesture/PopupPlayerGestureListener.kt   | 7 +++++--
 .../newpipe/player/notification/NotificationUtil.java      | 2 +-
 .../org/schabi/newpipe/views/player/CircleClipTapView.kt   | 6 +++---
 5 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 131bcaf0f..d6b49f232 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,7 +12,7 @@ plugins {
 }
 
 android {
-    compileSdk 33
+    compileSdk 34
     namespace 'org.schabi.newpipe'
 
     defaultConfig {
diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt
index 8acd70413..ff0bb269d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt
@@ -160,13 +160,12 @@ class MainPlayerGestureListener(
     }
 
     override fun onScroll(
-        initialEvent: MotionEvent,
+        initialEvent: MotionEvent?,
         movingEvent: MotionEvent,
         distanceX: Float,
         distanceY: Float
     ): Boolean {
-
-        if (!playerUi.isFullscreen) {
+        if (initialEvent == null || !playerUi.isFullscreen) {
             return false
         }
 
diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt
index 23edcaeb8..0b94bf364 100644
--- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt
+++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt
@@ -167,7 +167,7 @@ class PopupPlayerGestureListener(
     }
 
     override fun onFling(
-        e1: MotionEvent,
+        e1: MotionEvent?,
         e2: MotionEvent,
         velocityX: Float,
         velocityY: Float
@@ -218,11 +218,14 @@ class PopupPlayerGestureListener(
     }
 
     override fun onScroll(
-        initialEvent: MotionEvent,
+        initialEvent: MotionEvent?,
         movingEvent: MotionEvent,
         distanceX: Float,
         distanceY: Float
     ): Boolean {
+        if (initialEvent == null) {
+            return false
+        }
 
         if (isResizing) {
             return super.onScroll(initialEvent, movingEvent, distanceX, distanceY)
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
index 05c2e3af6..3fa7c2623 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
@@ -364,7 +364,7 @@ public final class NotificationUtil {
         final Bitmap thumbnail = player.getThumbnail();
         if (thumbnail == null || !showThumbnail) {
             // since the builder is reused, make sure the thumbnail is unset if there is not one
-            builder.setLargeIcon(null);
+            builder.setLargeIcon((Bitmap) null);
             return;
         }
 
diff --git a/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt b/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
index e3d142916..8554e7194 100644
--- a/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
+++ b/app/src/main/java/org/schabi/newpipe/views/player/CircleClipTapView.kt
@@ -80,10 +80,10 @@ class CircleClipTapView(context: Context?, attrs: AttributeSet) : View(context,
         updatePathShape()
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
 
-        canvas?.clipPath(shapePath)
-        canvas?.drawPath(shapePath, backgroundPaint)
+        canvas.clipPath(shapePath)
+        canvas.drawPath(shapePath, backgroundPaint)
     }
 }

From 76f1e588f7668e7ed613caac8d0a8cd053d2032b Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 16:35:56 -0400
Subject: [PATCH 063/141] Utilize BundleCompat and IntentCompat methods

---
 app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java | 3 ++-
 .../subscription/services/SubscriptionsExportService.java     | 3 ++-
 .../subscription/services/SubscriptionsImportService.java     | 3 ++-
 app/src/main/java/org/schabi/newpipe/util/StateSaver.java     | 4 +++-
 4 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
index 6b34c8e90..831a8cc4b 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
@@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.IntentCompat;
 
 import com.grack.nanojson.JsonWriter;
 
@@ -105,7 +106,7 @@ public class ErrorActivity extends AppCompatActivity {
             actionBar.setDisplayShowTitleEnabled(true);
         }
 
-        errorInfo = intent.getParcelableExtra(ERROR_INFO);
+        errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo.class);
 
         // important add guru meditation
         addGuruMeditation();
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
index d56d16f3c..54809068a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsExportService.java
@@ -25,6 +25,7 @@ import android.content.Intent;
 import android.net.Uri;
 import android.util.Log;
 
+import androidx.core.content.IntentCompat;
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
 import org.reactivestreams.Subscriber;
@@ -65,7 +66,7 @@ public class SubscriptionsExportService extends BaseImportExportService {
             return START_NOT_STICKY;
         }
 
-        final Uri path = intent.getParcelableExtra(KEY_FILE_PATH);
+        final Uri path = IntentCompat.getParcelableExtra(intent, KEY_FILE_PATH, Uri.class);
         if (path == null) {
             stopAndReportError(new IllegalStateException(
                     "Exporting to a file, but the path is null"),
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
index d624e1038..442c7fddb 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java
@@ -30,6 +30,7 @@ import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.content.IntentCompat;
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
 import org.reactivestreams.Subscriber;
@@ -108,7 +109,7 @@ public class SubscriptionsImportService extends BaseImportExportService {
         if (currentMode == CHANNEL_URL_MODE) {
             channelUrl = intent.getStringExtra(KEY_VALUE);
         } else {
-            final Uri uri = intent.getParcelableExtra(KEY_VALUE);
+            final Uri uri = IntentCompat.getParcelableExtra(intent, KEY_VALUE, Uri.class);
             if (uri == null) {
                 stopAndReportError(new IllegalStateException(
                         "Importing from input stream, but file path is null"),
diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java
index 91dc5f35b..61fdb602f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java
@@ -27,6 +27,7 @@ import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.os.BundleCompat;
 
 import org.schabi.newpipe.BuildConfig;
 import org.schabi.newpipe.MainActivity;
@@ -82,7 +83,8 @@ public final class StateSaver {
             return null;
         }
 
-        final SavedState savedState = outState.getParcelable(KEY_SAVED_STATE);
+        final SavedState savedState = BundleCompat.getParcelable(
+                outState, KEY_SAVED_STATE, SavedState.class);
         if (savedState == null) {
             return null;
         }

From f9fc1cd817ce989db6a636d973f09417c1ff0153 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <31027858+isira-seneviratne@users.noreply.github.com>
Date: Tue, 31 Oct 2023 16:36:13 -0400
Subject: [PATCH 064/141] Store/retrieve parcelable arrays as lists instead.

---
 ...agmentStatePagerAdapterMenuWorkaround.java | 12 ++---
 .../org/schabi/newpipe/about/AboutActivity.kt |  2 +-
 .../schabi/newpipe/about/LicenseFragment.kt   | 11 +++--
 .../newpipe/download/DownloadDialog.java      | 15 +++---
 .../java/org/schabi/newpipe/ktx/Bundle.kt     |  9 ++++
 .../giga/service/DownloadManagerService.java  | 47 +++++++++----------
 6 files changed, 53 insertions(+), 43 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt

diff --git a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java
index 8d87e90bd..8d03a1486 100644
--- a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java
+++ b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java
@@ -25,6 +25,7 @@ import android.view.ViewGroup;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.os.BundleCompat;
 import androidx.lifecycle.Lifecycle;
 import androidx.viewpager.widget.PagerAdapter;
 
@@ -284,7 +285,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
         Bundle state = null;
         if (!mSavedState.isEmpty()) {
             state = new Bundle();
-            state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0]));
+            state.putParcelableArrayList("states", mSavedState);
         }
         for (int i = 0; i < mFragments.size(); i++) {
             final Fragment f = mFragments.get(i);
@@ -311,13 +312,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
         if (state != null) {
             final Bundle bundle = (Bundle) state;
             bundle.setClassLoader(loader);
-            final Parcelable[] fss = bundle.getParcelableArray("states");
+            final var states = BundleCompat.getParcelableArrayList(bundle, "states",
+                    Fragment.SavedState.class);
             mSavedState.clear();
             mFragments.clear();
-            if (fss != null) {
-                for (final Parcelable parcelable : fss) {
-                    mSavedState.add((Fragment.SavedState) parcelable);
-                }
+            if (states != null) {
+                mSavedState.addAll(states);
             }
             final Iterable<String> keys = bundle.keySet();
             for (final String key : keys) {
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
index f1d4c26df..7f148e9b5 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
@@ -116,7 +116,7 @@ class AboutActivity : AppCompatActivity() {
         /**
          * List of all software components.
          */
-        private val SOFTWARE_COMPONENTS = arrayOf(
+        private val SOFTWARE_COMPONENTS = arrayListOf(
             SoftwareComponent(
                 "ACRA", "2013", "Kevin Gaudin",
                 "https://github.com/ACRA/acra", StandardLicenses.APACHE2
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
index a5cf2924a..9f5ad2a7a 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
@@ -18,6 +18,7 @@ import org.schabi.newpipe.BuildConfig
 import org.schabi.newpipe.R
 import org.schabi.newpipe.databinding.FragmentLicensesBinding
 import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
+import org.schabi.newpipe.ktx.parcelableArrayList
 import org.schabi.newpipe.util.Localization
 import org.schabi.newpipe.util.external_communication.ShareUtils
 
@@ -25,16 +26,15 @@ import org.schabi.newpipe.util.external_communication.ShareUtils
  * Fragment containing the software licenses.
  */
 class LicenseFragment : Fragment() {
-    private lateinit var softwareComponents: Array<SoftwareComponent>
+    private lateinit var softwareComponents: List<SoftwareComponent>
     private var activeSoftwareComponent: SoftwareComponent? = null
     private val compositeDisposable = CompositeDisposable()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        softwareComponents = arguments?.getParcelableArray(ARG_COMPONENTS) as Array<SoftwareComponent>
+        softwareComponents = arguments?.parcelableArrayList<SoftwareComponent>(ARG_COMPONENTS)!!
+            .sortedBy { it.name } // Sort components by name
         activeSoftwareComponent = savedInstanceState?.getSerializable(SOFTWARE_COMPONENT_KEY) as? SoftwareComponent
-        // Sort components by name
-        softwareComponents.sortBy { it.name }
     }
 
     override fun onDestroy() {
@@ -130,7 +130,8 @@ class LicenseFragment : Fragment() {
             StandardLicenses.GPL3,
             BuildConfig.VERSION_NAME
         )
-        fun newInstance(softwareComponents: Array<SoftwareComponent>): LicenseFragment {
+
+        fun newInstance(softwareComponents: ArrayList<SoftwareComponent>): LicenseFragment {
             val fragment = LicenseFragment()
             fragment.arguments = bundleOf(ARG_COMPONENTS to softwareComponents)
             return fragment
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index 2e0a421da..1375d661e 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -74,6 +74,7 @@ import org.schabi.newpipe.util.ThemeHelper;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -1052,7 +1053,7 @@ public class DownloadDialog extends DialogFragment
         final char kind;
         int threads = dialogBinding.threads.getProgress() + 1;
         final String[] urls;
-        final MissionRecoveryInfo[] recoveryInfo;
+        final List<MissionRecoveryInfo> recoveryInfo;
         String psName = null;
         String[] psArgs = null;
         long nearLength = 0;
@@ -1117,9 +1118,7 @@ public class DownloadDialog extends DialogFragment
             urls = new String[] {
                     selectedStream.getContent()
             };
-            recoveryInfo = new MissionRecoveryInfo[] {
-                    new MissionRecoveryInfo(selectedStream)
-            };
+            recoveryInfo = List.of(new MissionRecoveryInfo(selectedStream));
         } else {
             if (secondaryStream.getDeliveryMethod() != PROGRESSIVE_HTTP) {
                 throw new IllegalArgumentException("Unsupported stream delivery format"
@@ -1129,12 +1128,14 @@ public class DownloadDialog extends DialogFragment
             urls = new String[] {
                     selectedStream.getContent(), secondaryStream.getContent()
             };
-            recoveryInfo = new MissionRecoveryInfo[] {new MissionRecoveryInfo(selectedStream),
-                    new MissionRecoveryInfo(secondaryStream)};
+            recoveryInfo = List.of(
+                    new MissionRecoveryInfo(selectedStream),
+                    new MissionRecoveryInfo(secondaryStream)
+            );
         }
 
         DownloadManagerService.startMission(context, urls, storage, kind, threads,
-                currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
+                currentInfo.getUrl(), psName, psArgs, nearLength, new ArrayList<>(recoveryInfo));
 
         Toast.makeText(context, getString(R.string.download_has_started),
                 Toast.LENGTH_SHORT).show();
diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
new file mode 100644
index 000000000..61721d546
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
@@ -0,0 +1,9 @@
+package org.schabi.newpipe.ktx
+
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.core.os.BundleCompat
+
+inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
+    return BundleCompat.getParcelableArrayList(this, key, T::class.java)
+}
diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
index 009a4f4be..42ff3ca8c 100755
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -23,7 +23,6 @@ import android.os.Handler;
 import android.os.Handler.Callback;
 import android.os.IBinder;
 import android.os.Message;
-import android.os.Parcelable;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -36,6 +35,7 @@ import androidx.core.app.NotificationCompat.Builder;
 import androidx.core.app.PendingIntentCompat;
 import androidx.core.app.ServiceCompat;
 import androidx.core.content.ContextCompat;
+import androidx.core.content.IntentCompat;
 import androidx.preference.PreferenceManager;
 
 import org.schabi.newpipe.R;
@@ -49,6 +49,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 import us.shandian.giga.get.DownloadMission;
 import us.shandian.giga.get.MissionRecoveryInfo;
@@ -359,29 +360,29 @@ public class DownloadManagerService extends Service {
      */
     public static void startMission(Context context, String[] urls, StoredFileHelper storage,
                                     char kind, int threads, String source, String psName,
-                                    String[] psArgs, long nearLength, MissionRecoveryInfo[] recoveryInfo) {
-        Intent intent = new Intent(context, DownloadManagerService.class);
-        intent.setAction(Intent.ACTION_RUN);
-        intent.putExtra(EXTRA_URLS, urls);
-        intent.putExtra(EXTRA_KIND, kind);
-        intent.putExtra(EXTRA_THREADS, threads);
-        intent.putExtra(EXTRA_SOURCE, source);
-        intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName);
-        intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs);
-        intent.putExtra(EXTRA_NEAR_LENGTH, nearLength);
-        intent.putExtra(EXTRA_RECOVERY_INFO, recoveryInfo);
-
-        intent.putExtra(EXTRA_PARENT_PATH, storage.getParentUri());
-        intent.putExtra(EXTRA_PATH, storage.getUri());
-        intent.putExtra(EXTRA_STORAGE_TAG, storage.getTag());
+                                    String[] psArgs, long nearLength,
+                                    ArrayList<MissionRecoveryInfo> recoveryInfo) {
+        final Intent intent = new Intent(context, DownloadManagerService.class)
+                .setAction(Intent.ACTION_RUN)
+                .putExtra(EXTRA_URLS, urls)
+                .putExtra(EXTRA_KIND, kind)
+                .putExtra(EXTRA_THREADS, threads)
+                .putExtra(EXTRA_SOURCE, source)
+                .putExtra(EXTRA_POSTPROCESSING_NAME, psName)
+                .putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs)
+                .putExtra(EXTRA_NEAR_LENGTH, nearLength)
+                .putExtra(EXTRA_RECOVERY_INFO, recoveryInfo)
+                .putExtra(EXTRA_PARENT_PATH, storage.getParentUri())
+                .putExtra(EXTRA_PATH, storage.getUri())
+                .putExtra(EXTRA_STORAGE_TAG, storage.getTag());
 
         context.startService(intent);
     }
 
     private void startMission(Intent intent) {
         String[] urls = intent.getStringArrayExtra(EXTRA_URLS);
-        Uri path = intent.getParcelableExtra(EXTRA_PATH);
-        Uri parentPath = intent.getParcelableExtra(EXTRA_PARENT_PATH);
+        Uri path = IntentCompat.getParcelableExtra(intent, EXTRA_PATH, Uri.class);
+        Uri parentPath = IntentCompat.getParcelableExtra(intent, EXTRA_PARENT_PATH, Uri.class);
         int threads = intent.getIntExtra(EXTRA_THREADS, 1);
         char kind = intent.getCharExtra(EXTRA_KIND, '?');
         String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME);
@@ -389,7 +390,9 @@ public class DownloadManagerService extends Service {
         String source = intent.getStringExtra(EXTRA_SOURCE);
         long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0);
         String tag = intent.getStringExtra(EXTRA_STORAGE_TAG);
-        Parcelable[] parcelRecovery = intent.getParcelableArrayExtra(EXTRA_RECOVERY_INFO);
+        final var recovery = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_RECOVERY_INFO,
+                MissionRecoveryInfo.class);
+        Objects.requireNonNull(recovery);
 
         StoredFileHelper storage;
         try {
@@ -404,15 +407,11 @@ public class DownloadManagerService extends Service {
         else
             ps = Postprocessing.getAlgorithm(psName, psArgs);
 
-        MissionRecoveryInfo[] recovery = new MissionRecoveryInfo[parcelRecovery.length];
-        for (int i = 0; i < parcelRecovery.length; i++)
-            recovery[i] = (MissionRecoveryInfo) parcelRecovery[i];
-
         final DownloadMission mission = new DownloadMission(urls, storage, kind, ps);
         mission.threadCount = threads;
         mission.source = source;
         mission.nearLength = nearLength;
-        mission.recoveryInfo = recovery;
+        mission.recoveryInfo = recovery.toArray(MissionRecoveryInfo[]::new);
 
         if (ps != null)
             ps.setTemporalDir(DownloadManager.pickAvailableTemporalDir(this));

From 4a7fda95aee59cd9a1ef4444ab8d84570205f8dc Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 21:25:11 -0400
Subject: [PATCH 065/141] Update miscellaneous libraries

---
 app/build.gradle                              | 19 +++++++------------
 .../org/schabi/newpipe/database/Converters.kt |  2 +-
 .../local/feed/service/FeedEventManager.kt    |  2 +-
 .../subscription/dialog/FeedGroupDialog.kt    | 10 +++++-----
 .../dialog/FeedGroupDialogViewModel.kt        |  4 ++--
 .../local/subscription/FeedGroupIconTest.kt   |  4 ++--
 build.gradle                                  |  4 ++--
 7 files changed, 20 insertions(+), 25 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index d6b49f232..3705776b6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -118,7 +118,6 @@ ext {
 
     leakCanaryVersion = '2.12'
     stethoVersion = '1.6.0'
-    mockitoVersion = '4.0.0'
 }
 
 configurations {
@@ -237,13 +236,10 @@ dependencies {
     kapt "frankiesardo:icepick-processor:${icepickVersion}"
 
     // HTML parser
-    implementation "org.jsoup:jsoup:1.16.1"
+    implementation "org.jsoup:jsoup:1.16.2"
 
     // HTTP client
-    implementation "com.squareup.okhttp3:okhttp:4.11.0"
-    // okhttp3:4.11.0 introduces a vulnerability from com.squareup.okio:okio@3.3.0,
-    // remove com.squareup.okio:okio when updating okhttp
-    implementation "com.squareup.okio:okio:3.4.0"
+    implementation "com.squareup.okhttp3:okhttp:4.12.0"
 
     // Media player
     implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}"
@@ -272,19 +268,19 @@ dependencies {
     implementation "io.noties.markwon:linkify:${markwonVersion}"
 
     // Crash reporting
-    implementation "ch.acra:acra-core:5.10.1"
+    implementation "ch.acra:acra-core:5.11.3"
 
     // Properly restarting
     implementation 'com.jakewharton:process-phoenix:2.1.2'
 
     // Reactive extensions for Java VM
-    implementation "io.reactivex.rxjava3:rxjava:3.1.6"
+    implementation "io.reactivex.rxjava3:rxjava:3.1.8"
     implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
     // RxJava binding APIs for Android UI widgets
     implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
 
     // Date and time formatting
-    implementation "org.ocpsoft.prettytime:prettytime:5.0.6.Final"
+    implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final"
 
 /** Debugging **/
     // Memory leak detection
@@ -297,13 +293,12 @@ dependencies {
 
 /** Testing **/
     testImplementation 'junit:junit:4.13.2'
-    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
-    testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
+    testImplementation 'org.mockito:mockito-core:5.6.0'
 
     androidTestImplementation "androidx.test.ext:junit:1.1.5"
     androidTestImplementation "androidx.test:runner:1.5.2"
     androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
-    androidTestImplementation "org.assertj:assertj-core:3.23.1"
+    androidTestImplementation "org.assertj:assertj-core:3.24.2"
 }
 
 static String getGitWorkingBranch() {
diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.kt b/app/src/main/java/org/schabi/newpipe/database/Converters.kt
index 0eafcede1..6f705a5e4 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Converters.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/Converters.kt
@@ -47,6 +47,6 @@ object Converters {
 
     @TypeConverter
     fun feedGroupIconOf(id: Int): FeedGroupIcon {
-        return FeedGroupIcon.values().first { it.id == id }
+        return FeedGroupIcon.entries.first { it.id == id }
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
index 3d19de9c6..1c2826e7a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt
@@ -26,7 +26,7 @@ object FeedEventManager {
     }
 
     sealed class Event {
-        object IdleEvent : Event()
+        data object IdleEvent : Event()
         data class ProgressEvent(val currentProgress: Int = -1, val maxProgress: Int = -1, @StringRes val progressMessage: Int = 0) : Event() {
             constructor(@StringRes progressMessage: Int) : this(-1, -1, progressMessage)
         }
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
index 19c581c08..41761fb01 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt
@@ -55,10 +55,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
     private var groupSortOrder: Long = -1
 
     sealed class ScreenState : Serializable {
-        object InitialScreen : ScreenState()
-        object IconPickerScreen : ScreenState()
-        object SubscriptionsPickerScreen : ScreenState()
-        object DeleteScreen : ScreenState()
+        data object InitialScreen : ScreenState()
+        data object IconPickerScreen : ScreenState()
+        data object SubscriptionsPickerScreen : ScreenState()
+        data object DeleteScreen : ScreenState()
     }
 
     @State @JvmField var selectedIcon: FeedGroupIcon? = null
@@ -370,7 +370,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
 
     private fun setupIconPicker() {
         val groupAdapter = GroupieAdapter()
-        groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(it) })
+        groupAdapter.addAll(FeedGroupIcon.entries.map { PickerIconItem(it) })
 
         feedGroupCreateBinding.iconSelector.apply {
             layoutManager = GridLayoutManager(requireContext(), 7, RecyclerView.VERTICAL, false)
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
index eff1a4400..292bda394 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt
@@ -110,8 +110,8 @@ class FeedGroupDialogViewModel(
     }
 
     sealed class DialogEvent {
-        object ProcessingEvent : DialogEvent()
-        object SuccessEvent : DialogEvent()
+        data object ProcessingEvent : DialogEvent()
+        data object SuccessEvent : DialogEvent()
     }
 
     data class Filter(val query: String, val showOnlyUngrouped: Boolean)
diff --git a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
index 1479c3d1e..2fc44d9b7 100644
--- a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
+++ b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt
@@ -9,7 +9,7 @@ class FeedGroupIconTest {
     fun `No gaps and repeated ids`() {
         val usedIds = HashSet<Int>()
 
-        for ((shouldBeId, currentIcon) in FeedGroupIcon.values().withIndex()) {
+        for ((shouldBeId, currentIcon) in FeedGroupIcon.entries.withIndex()) {
             val added = usedIds.add(currentIcon.id)
             assertTrue("Repeated ids (current item: ${currentIcon.name} - ${currentIcon.id})", added)
 
@@ -24,7 +24,7 @@ class FeedGroupIconTest {
     fun `No icons pointing to the same attr`() {
         val usedIcons = HashSet<Int>()
 
-        for (groupIcon in FeedGroupIcon.values()) {
+        for (groupIcon in FeedGroupIcon.entries) {
             val added = usedIcons.add(groupIcon.drawableResource)
             assertTrue("Repeated icon (current item: ${groupIcon.name} - ${groupIcon.id})", added)
         }
diff --git a/build.gradle b/build.gradle
index f1c861c9f..2f3d292bb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.8.22'
+    ext.kotlin_version = '1.9.10'
     repositories {
         google()
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:8.1.1'
+        classpath 'com.android.tools.build:gradle:8.1.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong

From 0f81a0504ce88682f6b0810f81807676ade64514 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 21:28:24 -0400
Subject: [PATCH 066/141] Use 'tasks.register' for Gradle tasks

---
 app/build.gradle | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 3705776b6..1e109259d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -132,7 +132,7 @@ checkstyle {
     toolVersion = checkstyleVersion
 }
 
-task runCheckstyle(type: Checkstyle) {
+tasks.register('runCheckstyle', Checkstyle) {
     source 'src'
     include '**/*.java'
     exclude '**/gen/**'
@@ -153,7 +153,7 @@ task runCheckstyle(type: Checkstyle) {
 def outputDir = "${project.buildDir}/reports/ktlint/"
 def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
 
-task runKtlint(type: JavaExec) {
+tasks.register('runKtlint', JavaExec) {
     inputs.files(inputFiles)
     outputs.dir(outputDir)
     getMainClass().set("com.pinterest.ktlint.Main")
@@ -162,7 +162,7 @@ task runKtlint(type: JavaExec) {
     jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
 }
 
-task formatKtlint(type: JavaExec) {
+tasks.register('formatKtlint', JavaExec) {
     inputs.files(inputFiles)
     outputs.dir(outputDir)
     getMainClass().set("com.pinterest.ktlint.Main")

From b31d3831e6b8da432981491f7a3c7eb7e6f51ad9 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Tue, 31 Oct 2023 22:39:11 -0400
Subject: [PATCH 067/141] Change Converters to class to fix build

---
 app/src/main/java/org/schabi/newpipe/database/Converters.kt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.kt b/app/src/main/java/org/schabi/newpipe/database/Converters.kt
index 6f705a5e4..ec097cc1b 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Converters.kt
+++ b/app/src/main/java/org/schabi/newpipe/database/Converters.kt
@@ -7,7 +7,7 @@ import java.time.Instant
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 
-object Converters {
+class Converters {
     /**
      * Convert a long value to a [OffsetDateTime].
      *

From 1e7e2109d232b5f8ec6ee905a05c4c246db5b5e6 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Fri, 17 Nov 2023 16:58:30 -0500
Subject: [PATCH 068/141] Exclude RxJava file from META-INF

---
 app/build.gradle | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 1e109259d..641b5f9a4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -98,7 +98,9 @@ android {
         resources {
             // remove two files which belong to jsoup
             // no idea how they ended up in the META-INF dir...
-            excludes += ['META-INF/README.md', 'META-INF/CHANGES']
+            excludes += ['META-INF/README.md', 'META-INF/CHANGES',
+                         // 'COPYRIGHT' belongs to RxJava...
+                         'META-INF/COPYRIGHT']
         }
     }
 }

From edd4f6b9f3efa087d1775554977fe4fe62e3a191 Mon Sep 17 00:00:00 2001
From: TacoTheDank <SkytkRSfan3895@gmail.com>
Date: Fri, 17 Nov 2023 17:09:35 -0500
Subject: [PATCH 069/141] Update Studio and desugaring versions

---
 app/build.gradle | 2 +-
 build.gradle     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 641b5f9a4..fa6c607a6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -190,7 +190,7 @@ sonar {
 
 dependencies {
 /** Desugaring **/
-    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.3'
+    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
 
 /** NewPipe libraries **/
     // You can use a local version by uncommenting a few lines in settings.gradle
diff --git a/build.gradle b/build.gradle
index 2f3d292bb..fa94e94ab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:8.1.2'
+        classpath 'com.android.tools.build:gradle:8.1.4'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong

From 5d09a883352382ff821bbab6db59d097af80e64e Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 23 Dec 2023 11:58:58 +0100
Subject: [PATCH 070/141] Update more libraries

---
 app/build.gradle | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index fa6c607a6..a22228556 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -109,7 +109,7 @@ ext {
     checkstyleVersion = '10.12.1'
 
     androidxLifecycleVersion = '2.6.2'
-    androidxRoomVersion = '2.5.2'
+    androidxRoomVersion = '2.6.1'
     androidxWorkVersion = '2.8.1'
 
     icepickVersion = '3.2.0'
@@ -214,11 +214,11 @@ dependencies {
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
     implementation 'androidx.core:core-ktx:1.12.0'
     implementation 'androidx.documentfile:documentfile:1.0.1'
-    implementation 'androidx.fragment:fragment-ktx:1.6.1'
+    implementation 'androidx.fragment:fragment-ktx:1.6.2'
     implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
     implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
-    implementation 'androidx.media:media:1.6.0'
+    implementation 'androidx.media:media:1.7.0'
     implementation 'androidx.preference:preference:1.2.1'
     implementation 'androidx.recyclerview:recyclerview:1.3.2'
     implementation "androidx.room:room-runtime:${androidxRoomVersion}"
@@ -230,7 +230,7 @@ dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
     implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
     implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
-    implementation 'com.google.android.material:material:1.9.0'
+    implementation 'com.google.android.material:material:1.11.0'
 
 /** Third-party libraries **/
     // Instance state boilerplate elimination

From cda4b3faaab916d78c136be1bd73aff5e8305e5f Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 23 Dec 2023 12:01:50 +0100
Subject: [PATCH 071/141] Update AGP and Gradle

---
 build.gradle                             | 2 +-
 gradle/wrapper/gradle-wrapper.properties | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/build.gradle b/build.gradle
index fa94e94ab..6d19a6f8a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:8.1.4'
+        classpath 'com.android.tools.build:gradle:8.2.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2c3425d49..d022615ff 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

From 6c9955755339cae73d1f90567bf52999779fcb96 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Fri, 12 May 2023 11:29:09 +0200
Subject: [PATCH 072/141] Add playlist description to PlaylistFragment

---
 .../list/playlist/PlaylistFragment.java         | 17 +++++++++++++++++
 app/src/main/res/layout/playlist_header.xml     | 11 ++++++++++-
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index e82a984d5..95cead389 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -1,7 +1,9 @@
 package org.schabi.newpipe.fragments.list.playlist;
 
+import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
 import static org.schabi.newpipe.ktx.ViewUtils.animate;
 import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
+import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
 
 import android.content.Context;
 import android.os.Bundle;
@@ -17,6 +19,7 @@ import android.view.ViewGroup;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.text.HtmlCompat;
 
 import com.google.android.material.shape.CornerFamily;
 import com.google.android.material.shape.ShapeAppearanceModel;
@@ -37,6 +40,7 @@ import org.schabi.newpipe.extractor.ListExtractor;
 import org.schabi.newpipe.extractor.ServiceList;
 import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
 import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
+import org.schabi.newpipe.extractor.stream.Description;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
 import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
@@ -51,6 +55,7 @@ import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.image.PicassoHelper;
 import org.schabi.newpipe.util.external_communication.ShareUtils;
 import org.schabi.newpipe.util.PlayButtonHelper;
+import org.schabi.newpipe.util.text.TextLinkifier;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -321,6 +326,18 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
         headerBinding.playlistStreamCount.setText(Localization
                 .localizeStreamCount(getContext(), result.getStreamCount()));
 
+        final Description description = result.getDescription();
+        if (description != null && description != Description.EMPTY_DESCRIPTION
+                && !isBlank(description.getContent())) {
+            TextLinkifier.fromDescription(headerBinding.playlistDescription,
+                    description, HtmlCompat.FROM_HTML_MODE_LEGACY,
+                    result.getService(), result.getUrl(),
+                    disposables, SET_LINK_MOVEMENT_METHOD);
+            headerBinding.playlistDescription.setVisibility(View.VISIBLE);
+        } else {
+            headerBinding.playlistDescription.setVisibility(View.GONE);
+        }
+
         if (!result.getErrors().isEmpty()) {
             showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
                     result.getUrl(), result));
diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml
index 9c038db3a..2d6f30676 100644
--- a/app/src/main/res/layout/playlist_header.xml
+++ b/app/src/main/res/layout/playlist_header.xml
@@ -80,10 +80,19 @@
             tools:text="234 videos" />
     </RelativeLayout>
 
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/playlist_description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/playlist_meta"
+        android:paddingHorizontal="@dimen/video_item_search_padding"
+        android:paddingTop="6dp"
+        tools:text="This is a multiline playlist description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
+
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_below="@id/playlist_meta">
+        android:layout_below="@id/playlist_description">
 
         <include
             android:id="@+id/playlist_control"

From 65eb631711c0c647246347e0e920622e4ecd6db4 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Tue, 20 Jun 2023 15:56:43 +0200
Subject: [PATCH 073/141] Ellipsize playlist description if it is longer than 5
 lines

The description can be expanded / collapsed via a "show more" / "show less" button.
---
 .../list/playlist/PlaylistFragment.java       |  30 ++-
 .../holder/CommentInfoItemHolder.java         | 123 ++---------
 .../newpipe/util/text/TextEllipsizer.java     | 195 ++++++++++++++++++
 app/src/main/res/layout/playlist_header.xml   |  17 +-
 app/src/main/res/values/strings.xml           |   2 +
 5 files changed, 249 insertions(+), 118 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index 95cead389..ab3963d61 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -3,7 +3,7 @@ package org.schabi.newpipe.fragments.list.playlist;
 import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
 import static org.schabi.newpipe.ktx.ViewUtils.animate;
 import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
-import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
+import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
 
 import android.content.Context;
 import android.os.Bundle;
@@ -19,7 +19,6 @@ import android.view.ViewGroup;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.content.res.AppCompatResources;
-import androidx.core.text.HtmlCompat;
 
 import com.google.android.material.shape.CornerFamily;
 import com.google.android.material.shape.ShapeAppearanceModel;
@@ -52,10 +51,10 @@ import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
 import org.schabi.newpipe.util.ExtractorHelper;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.image.PicassoHelper;
-import org.schabi.newpipe.util.external_communication.ShareUtils;
 import org.schabi.newpipe.util.PlayButtonHelper;
-import org.schabi.newpipe.util.text.TextLinkifier;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.text.TextEllipsizer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -329,13 +328,24 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
         final Description description = result.getDescription();
         if (description != null && description != Description.EMPTY_DESCRIPTION
                 && !isBlank(description.getContent())) {
-            TextLinkifier.fromDescription(headerBinding.playlistDescription,
-                    description, HtmlCompat.FROM_HTML_MODE_LEGACY,
-                    result.getService(), result.getUrl(),
-                    disposables, SET_LINK_MOVEMENT_METHOD);
-            headerBinding.playlistDescription.setVisibility(View.VISIBLE);
+            final TextEllipsizer ellipsizer = new TextEllipsizer(
+                    headerBinding.playlistDescription, 5, getServiceById(result.getServiceId()));
+            ellipsizer.setStateChangeListener(isEllipsized ->
+                headerBinding.playlistDescriptionReadMore.setText(
+                        Boolean.TRUE.equals(isEllipsized) ? R.string.show_more : R.string.show_less
+                ));
+            ellipsizer.setOnContentChanged(canBeEllipsized -> {
+                headerBinding.playlistDescriptionReadMore.setVisibility(
+                        Boolean.TRUE.equals(canBeEllipsized) ? View.VISIBLE : View.GONE);
+                if (Boolean.TRUE.equals(canBeEllipsized)) {
+                    ellipsizer.ellipsize();
+                }
+            });
+            ellipsizer.setContent(description);
+            headerBinding.playlistDescriptionReadMore.setOnClickListener(v -> ellipsizer.toggle());
         } else {
             headerBinding.playlistDescription.setVisibility(View.GONE);
+            headerBinding.playlistDescriptionReadMore.setVisibility(View.GONE);
         }
 
         if (!result.getErrors().isEmpty()) {
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
index 8327b398b..a3f0384ad 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentInfoItemHolder.java
@@ -1,10 +1,7 @@
 package org.schabi.newpipe.info_list.holder;
 
-import static android.text.TextUtils.isEmpty;
 import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
 
-import android.graphics.Paint;
-import android.text.Layout;
 import android.text.method.LinkMovementMethod;
 import android.text.style.URLSpan;
 import android.view.View;
@@ -15,42 +12,28 @@ import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.text.HtmlCompat;
 import androidx.fragment.app.FragmentActivity;
 
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.extractor.InfoItem;
-import org.schabi.newpipe.extractor.StreamingService;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
-import org.schabi.newpipe.extractor.stream.Description;
 import org.schabi.newpipe.info_list.InfoItemBuilder;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.DeviceUtils;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
 import org.schabi.newpipe.util.image.ImageStrategy;
 import org.schabi.newpipe.util.image.PicassoHelper;
-import org.schabi.newpipe.util.external_communication.ShareUtils;
 import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
-import org.schabi.newpipe.util.text.TextLinkifier;
-
-import java.util.function.Consumer;
-
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import org.schabi.newpipe.util.text.TextEllipsizer;
 
 public class CommentInfoItemHolder extends InfoItemHolder {
-    private static final String ELLIPSIS = "…";
 
     private static final int COMMENT_DEFAULT_LINES = 2;
-    private static final int COMMENT_EXPANDED_LINES = 1000;
-
     private final int commentHorizontalPadding;
     private final int commentVerticalPadding;
 
-    private final Paint paintAtContentSize;
-    private final float ellipsisWidthPx;
-
     private final RelativeLayout itemRoot;
     private final ImageView itemThumbnailView;
     private final TextView itemContentView;
@@ -61,13 +44,8 @@ public class CommentInfoItemHolder extends InfoItemHolder {
     private final ImageView itemPinnedView;
     private final Button repliesButton;
 
-    private final CompositeDisposable disposables = new CompositeDisposable();
-    @Nullable
-    private Description commentText;
-    @Nullable
-    private StreamingService streamService;
-    @Nullable
-    private String streamUrl;
+    @NonNull
+    private final TextEllipsizer textEllipsizer;
 
     public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,
                                  final ViewGroup parent) {
@@ -88,9 +66,14 @@ public class CommentInfoItemHolder extends InfoItemHolder {
         commentVerticalPadding = (int) infoItemBuilder.getContext()
                 .getResources().getDimension(R.dimen.comments_vertical_padding);
 
-        paintAtContentSize = new Paint();
-        paintAtContentSize.setTextSize(itemContentView.getTextSize());
-        ellipsisWidthPx = paintAtContentSize.measureText(ELLIPSIS);
+        textEllipsizer = new TextEllipsizer(itemContentView, COMMENT_DEFAULT_LINES, null);
+        textEllipsizer.setStateChangeListener(isEllipsized -> {
+            if (Boolean.TRUE.equals(isEllipsized)) {
+                denyLinkFocus();
+            } else {
+                determineMovementMethod();
+            }
+        });
     }
 
     @Override
@@ -139,16 +122,16 @@ public class CommentInfoItemHolder extends InfoItemHolder {
 
 
         // setup comment content and click listeners to expand/ellipsize it
-        streamService = getServiceById(item.getServiceId());
-        streamUrl = item.getUrl();
-        commentText = item.getCommentText();
-        ellipsize();
+        textEllipsizer.setStreamingService(getServiceById(item.getServiceId()));
+        textEllipsizer.setStreamUrl(item.getUrl());
+        textEllipsizer.setContent(item.getCommentText());
+        textEllipsizer.ellipsize();
 
         //noinspection ClickableViewAccessibility
         itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
 
         itemView.setOnClickListener(view -> {
-            toggleEllipsize();
+            textEllipsizer.toggle();
             if (itemBuilder.getOnCommentsSelectedListener() != null) {
                 itemBuilder.getOnCommentsSelectedListener().selected(item);
             }
@@ -202,76 +185,4 @@ public class CommentInfoItemHolder extends InfoItemHolder {
             denyLinkFocus();
         }
     }
-
-    private void ellipsize() {
-        itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
-        linkifyCommentContentView(v -> {
-            boolean hasEllipsis = false;
-
-            final CharSequence charSeqText = itemContentView.getText();
-            if (charSeqText != null && itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
-                // Note that converting to String removes spans (i.e. links), but that's something
-                // we actually want since when the text is ellipsized we want all clicks on the
-                // comment to expand the comment, not to open links.
-                final String text = charSeqText.toString();
-
-                final Layout layout = itemContentView.getLayout();
-                final float lineWidth = layout.getLineWidth(COMMENT_DEFAULT_LINES - 1);
-                final float layoutWidth = layout.getWidth();
-                final int lineStart = layout.getLineStart(COMMENT_DEFAULT_LINES - 1);
-                final int lineEnd = layout.getLineEnd(COMMENT_DEFAULT_LINES - 1);
-
-                // remove characters up until there is enough space for the ellipsis
-                // (also summing 2 more pixels, just to be sure to avoid float rounding errors)
-                int end = lineEnd;
-                float removedCharactersWidth = 0.0f;
-                while (lineWidth - removedCharactersWidth + ellipsisWidthPx + 2.0f > layoutWidth
-                        && end >= lineStart) {
-                    end -= 1;
-                    // recalculate each time to account for ligatures or other similar things
-                    removedCharactersWidth = paintAtContentSize.measureText(
-                            text.substring(end, lineEnd));
-                }
-
-                // remove trailing spaces and newlines
-                while (end > 0 && Character.isWhitespace(text.charAt(end - 1))) {
-                    end -= 1;
-                }
-
-                final String newVal = text.substring(0, end) + ELLIPSIS;
-                itemContentView.setText(newVal);
-                hasEllipsis = true;
-            }
-
-            itemContentView.setMaxLines(COMMENT_DEFAULT_LINES);
-            if (hasEllipsis) {
-                denyLinkFocus();
-            } else {
-                determineMovementMethod();
-            }
-        });
-    }
-
-    private void toggleEllipsize() {
-        final CharSequence text = itemContentView.getText();
-        if (!isEmpty(text) && text.charAt(text.length() - 1) == ELLIPSIS.charAt(0)) {
-            expand();
-        } else if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
-            ellipsize();
-        }
-    }
-
-    private void expand() {
-        itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
-        linkifyCommentContentView(v -> determineMovementMethod());
-    }
-
-    private void linkifyCommentContentView(@Nullable final Consumer<TextView> onCompletion) {
-        disposables.clear();
-        if (commentText != null) {
-            TextLinkifier.fromDescription(itemContentView, commentText,
-                    HtmlCompat.FROM_HTML_MODE_LEGACY, streamService, streamUrl, disposables,
-                    onCompletion);
-        }
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java b/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java
new file mode 100644
index 000000000..41084926b
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java
@@ -0,0 +1,195 @@
+package org.schabi.newpipe.util.text;
+
+import android.graphics.Paint;
+import android.text.Layout;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.text.HtmlCompat;
+
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.stream.Description;
+
+import java.util.function.Consumer;
+
+
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
+/**
+ * <p>Class to ellipsize text inside a {@link TextView}.</p>
+ * This class provides all utils to automatically ellipsize and expand a text
+ */
+public final class TextEllipsizer {
+    private static final int EXPANDED_LINES = Integer.MAX_VALUE;
+    private static final String ELLIPSIS = "…";
+
+    @NonNull private final CompositeDisposable disposable = new CompositeDisposable();
+
+    @NonNull private final TextView view;
+    private final int maxLines;
+    @NonNull private Description content;
+    @Nullable private StreamingService streamingService;
+    @Nullable private String streamUrl;
+    private boolean isEllipsized = false;
+    @Nullable private Boolean caBeEllipsized = null;
+
+    @NonNull private final Paint paintAtContentSize = new Paint();
+    private final float ellipsisWidthPx;
+    @Nullable private Consumer<Boolean> stateChangeListener = null;
+    @Nullable private Consumer<Boolean> onContentChanged;
+
+    public TextEllipsizer(@NonNull final TextView view,
+                          final int maxLines,
+                          @Nullable final StreamingService streamingService) {
+        this.view = view;
+        this.maxLines = maxLines;
+        this.streamingService = streamingService;
+
+        paintAtContentSize.setTextSize(view.getTextSize());
+        ellipsisWidthPx = paintAtContentSize.measureText(ELLIPSIS);
+    }
+
+    public void setOnContentChanged(@Nullable final Consumer<Boolean> onContentChanged) {
+        this.onContentChanged = onContentChanged;
+    }
+
+    public void setContent(@NonNull final Description content) {
+        this.content = content;
+        caBeEllipsized = null;
+        linkifyContentView(v -> {
+            final int currentMaxLines = view.getMaxLines();
+            view.setMaxLines(EXPANDED_LINES);
+            caBeEllipsized = view.getLineCount() > maxLines;
+            view.setMaxLines(currentMaxLines);
+            if (onContentChanged != null) {
+                onContentChanged.accept(caBeEllipsized);
+            }
+        });
+    }
+
+    public void setStreamUrl(@Nullable final String streamUrl) {
+        this.streamUrl = streamUrl;
+    }
+
+    public void setStreamingService(@NonNull final StreamingService streamingService) {
+        this.streamingService = streamingService;
+    }
+
+    /**
+     * Expand the {@link TextEllipsizer#content} to its full length.
+     */
+    public void expand() {
+        view.setMaxLines(EXPANDED_LINES);
+        linkifyContentView(v -> isEllipsized = false);
+    }
+
+    /**
+     * Shorten the {@link TextEllipsizer#content} to the given number of
+     * {@link TextEllipsizer#maxLines maximum lines} and add trailing '{@code …}'
+     * if the text was shorted.
+     */
+    public void ellipsize() {
+        // expand text to see whether it is necessary to ellipsize the text
+        view.setMaxLines(EXPANDED_LINES);
+        linkifyContentView(v -> {
+            final CharSequence charSeqText = view.getText();
+            if (charSeqText != null && view.getLineCount() > maxLines) {
+                // Note that converting to String removes spans (i.e. links), but that's something
+                // we actually want since when the text is ellipsized we want all clicks on the
+                // comment to expand the comment, not to open links.
+                final String text = charSeqText.toString();
+
+                final Layout layout = view.getLayout();
+                final float lineWidth = layout.getLineWidth(maxLines - 1);
+                final float layoutWidth = layout.getWidth();
+                final int lineStart = layout.getLineStart(maxLines - 1);
+                final int lineEnd = layout.getLineEnd(maxLines - 1);
+
+                // remove characters up until there is enough space for the ellipsis
+                // (also summing 2 more pixels, just to be sure to avoid float rounding errors)
+                int end = lineEnd;
+                float removedCharactersWidth = 0.0f;
+                while (lineWidth - removedCharactersWidth + ellipsisWidthPx + 2.0f > layoutWidth
+                        && end >= lineStart) {
+                    end -= 1;
+                    // recalculate each time to account for ligatures or other similar things
+                    removedCharactersWidth = paintAtContentSize.measureText(
+                            text.substring(end, lineEnd));
+                }
+
+                // remove trailing spaces and newlines
+                while (end > 0 && Character.isWhitespace(text.charAt(end - 1))) {
+                    end -= 1;
+                }
+
+                final String newVal = text.substring(0, end) + ELLIPSIS;
+                view.setText(newVal);
+                isEllipsized = true;
+            } else {
+                isEllipsized = false;
+            }
+            view.setMaxLines(maxLines);
+        });
+    }
+
+    /**
+     * Toggle the view between the ellipsed and expanded state.
+     */
+    public void toggle() {
+        if (isEllipsized) {
+            expand();
+        } else {
+            ellipsize();
+        }
+    }
+
+    /**
+     * Whether the {@link view} can be ellipsized.
+     * This is only the case when the {@link content} has more lines
+     * than allowed via {@link maxLines}.
+     * @return {@code true} if the {@link content} has more lines than allowed via {@link maxLines}
+     * and thus can be shortened, {@code false} if the {@code content} fits into the {@link view}
+     * without being shortened and {@code null} if the initialization is not completed yet.
+     */
+    @Nullable
+    public Boolean canBeEllipsized() {
+        return caBeEllipsized;
+    }
+
+    private void linkifyContentView(final Consumer<View> consumer) {
+        final boolean oldState = isEllipsized;
+        disposable.clear();
+        TextLinkifier.fromDescription(view, content,
+                HtmlCompat.FROM_HTML_MODE_LEGACY, streamingService, streamUrl, disposable,
+                v -> {
+                    consumer.accept(v);
+                    notifyStateChangeListener(oldState);
+                });
+
+    }
+
+    /**
+     * Add a listener which is called when the given content is changed,
+     * either from <em>ellipsized</em> to <em>full</em> or vice versa.
+     * @param listener The listener to be called.
+     *                 The Boolean parameter is the new state.
+     *                 <em>Ellipsized</em> content is represented as {@code true},
+     *                 normal or <em>full</em> content by {@code false}.
+     */
+    public void setStateChangeListener(final Consumer<Boolean> listener) {
+        this.stateChangeListener = listener;
+    }
+
+    public void removeStateChangeListener() {
+        this.stateChangeListener = null;
+    }
+
+    private void notifyStateChangeListener(final boolean oldState) {
+        if (oldState != isEllipsized && stateChangeListener != null) {
+            stateChangeListener.accept(isEllipsized);
+        }
+    }
+
+}
diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml
index 2d6f30676..c761240d9 100644
--- a/app/src/main/res/layout/playlist_header.xml
+++ b/app/src/main/res/layout/playlist_header.xml
@@ -87,12 +87,25 @@
         android:layout_below="@id/playlist_meta"
         android:paddingHorizontal="@dimen/video_item_search_padding"
         android:paddingTop="6dp"
-        tools:text="This is a multiline playlist description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
+        android:maxLines="5"
+        tools:text="This is a multiline playlist description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" />
+
+    <org.schabi.newpipe.views.NewPipeTextView
+        android:id="@+id/playlist_description_read_more"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/playlist_description"
+        android:gravity="end"
+        android:paddingHorizontal="@dimen/video_item_search_padding"
+        android:paddingTop="6dp"
+        android:text="@string/show_more"
+        android:layout_marginBottom="6dp"
+        android:textColor="?attr/colorPrimary" />
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_below="@id/playlist_description">
+        android:layout_below="@id/playlist_description_read_more">
 
         <include
             android:id="@+id/playlist_control"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d94abfe70..ab80c3eb5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -842,4 +842,6 @@
         <item quantity="one">%s reply</item>
         <item quantity="other">%s replies</item>
     </plurals>
+    <string name="show_more">Show more</string>
+    <string name="show_less">Show less</string>
 </resources>

From 9ff1b5230fda1411d031cca528a2b6febed788e4 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 23 Dec 2023 17:53:27 +0100
Subject: [PATCH 074/141] Improve TextEllipsizer class

---
 .../newpipe/util/text/TextEllipsizer.java     | 34 +++++++++----------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java b/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java
index 41084926b..184b73304 100644
--- a/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TextEllipsizer.java
@@ -33,7 +33,7 @@ public final class TextEllipsizer {
     @Nullable private StreamingService streamingService;
     @Nullable private String streamUrl;
     private boolean isEllipsized = false;
-    @Nullable private Boolean caBeEllipsized = null;
+    @Nullable private Boolean canBeEllipsized = null;
 
     @NonNull private final Paint paintAtContentSize = new Paint();
     private final float ellipsisWidthPx;
@@ -45,6 +45,7 @@ public final class TextEllipsizer {
                           @Nullable final StreamingService streamingService) {
         this.view = view;
         this.maxLines = maxLines;
+        this.content = Description.EMPTY_DESCRIPTION;
         this.streamingService = streamingService;
 
         paintAtContentSize.setTextSize(view.getTextSize());
@@ -57,14 +58,14 @@ public final class TextEllipsizer {
 
     public void setContent(@NonNull final Description content) {
         this.content = content;
-        caBeEllipsized = null;
+        canBeEllipsized = null;
         linkifyContentView(v -> {
             final int currentMaxLines = view.getMaxLines();
             view.setMaxLines(EXPANDED_LINES);
-            caBeEllipsized = view.getLineCount() > maxLines;
+            canBeEllipsized = view.getLineCount() > maxLines;
             view.setMaxLines(currentMaxLines);
             if (onContentChanged != null) {
-                onContentChanged.accept(caBeEllipsized);
+                onContentChanged.accept(canBeEllipsized);
             }
         });
     }
@@ -135,7 +136,7 @@ public final class TextEllipsizer {
     }
 
     /**
-     * Toggle the view between the ellipsed and expanded state.
+     * Toggle the view between the ellipsized and expanded state.
      */
     public void toggle() {
         if (isEllipsized) {
@@ -146,16 +147,17 @@ public final class TextEllipsizer {
     }
 
     /**
-     * Whether the {@link view} can be ellipsized.
-     * This is only the case when the {@link content} has more lines
-     * than allowed via {@link maxLines}.
-     * @return {@code true} if the {@link content} has more lines than allowed via {@link maxLines}
-     * and thus can be shortened, {@code false} if the {@code content} fits into the {@link view}
-     * without being shortened and {@code null} if the initialization is not completed yet.
+     * Whether the {@link #view} can be ellipsized.
+     * This is only the case when the {@link #content} has more lines
+     * than allowed via {@link #maxLines}.
+     * @return {@code true} if the {@link #content} has more lines than allowed via
+     * {@link #maxLines} and thus can be shortened, {@code false} if the {@code content} fits into
+     * the {@link #view} without being shortened and {@code null} if the initialization is not
+     * completed yet.
      */
     @Nullable
     public Boolean canBeEllipsized() {
-        return caBeEllipsized;
+        return canBeEllipsized;
     }
 
     private void linkifyContentView(final Consumer<View> consumer) {
@@ -173,19 +175,15 @@ public final class TextEllipsizer {
     /**
      * Add a listener which is called when the given content is changed,
      * either from <em>ellipsized</em> to <em>full</em> or vice versa.
-     * @param listener The listener to be called.
+     * @param listener The listener to be called, or {@code null} to remove it.
      *                 The Boolean parameter is the new state.
      *                 <em>Ellipsized</em> content is represented as {@code true},
      *                 normal or <em>full</em> content by {@code false}.
      */
-    public void setStateChangeListener(final Consumer<Boolean> listener) {
+    public void setStateChangeListener(@Nullable final Consumer<Boolean> listener) {
         this.stateChangeListener = listener;
     }
 
-    public void removeStateChangeListener() {
-        this.stateChangeListener = null;
-    }
-
     private void notifyStateChangeListener(final boolean oldState) {
         if (oldState != isEllipsized && stateChangeListener != null) {
             stateChangeListener.accept(isEllipsized);

From 9220e324638284404d2bd234a460b0ce6bade4f1 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 10:54:31 +0100
Subject: [PATCH 075/141] Fix FeedDAOTest

---
 .../java/org/schabi/newpipe/database/FeedDAOTest.kt       | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt
index 9fa87c31b..893ae82b7 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt
@@ -85,7 +85,13 @@ class FeedDAOTest {
 
     private fun assertEqual(streams: List<StreamWithState>?, allowedStreams: List<StreamEntity>) {
         assertNotNull(streams)
-        assertEquals(allowedStreams, streams!!.stream().map { it.stream }.toList().sortedBy { it.uid })
+        assertEquals(
+            allowedStreams,
+            streams!!
+                .map { it.stream }
+                .sortedBy { it.uid }
+                .toList()
+        )
     }
 
     private fun setupUnlinkDelete(time: String) {

From 5a4dae207001da7e3bd3f65dcb7dfb89b6c82ae8 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 11:36:12 +0100
Subject: [PATCH 076/141] Fix settings_notification.xml indentation

---
 .../main/res/layout/settings_notification.xml | 111 +++++++++---------
 1 file changed, 55 insertions(+), 56 deletions(-)

diff --git a/app/src/main/res/layout/settings_notification.xml b/app/src/main/res/layout/settings_notification.xml
index d4af3fe71..e5d73a4ae 100644
--- a/app/src/main/res/layout/settings_notification.xml
+++ b/app/src/main/res/layout/settings_notification.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <androidx.constraintlayout.widget.ConstraintLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -8,64 +7,64 @@
         android:paddingTop="16dp">
 
     <org.schabi.newpipe.views.NewPipeTextView
-            android:id="@+id/textView"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="16dp"
-            android:clickable="false"
-            android:focusable="false"
-            android:gravity="center"
-            android:text="@string/notification_actions_summary"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.0"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
+        android:id="@+id/textView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:clickable="false"
+        android:focusable="false"
+        android:gravity="center"
+        android:text="@string/notification_actions_summary"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
 
-        <include
-            android:id="@+id/notificationAction0"
-            layout="@layout/settings_notification_action"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="8dp"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/textView" />
+    <include
+        android:id="@+id/notificationAction0"
+        layout="@layout/settings_notification_action"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView" />
 
-        <include
-            android:id="@+id/notificationAction1"
-            layout="@layout/settings_notification_action"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/notificationAction0" />
+    <include
+        android:id="@+id/notificationAction1"
+        layout="@layout/settings_notification_action"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/notificationAction0" />
 
-        <include
-            android:id="@+id/notificationAction2"
-            layout="@layout/settings_notification_action"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/notificationAction1" />
+    <include
+        android:id="@+id/notificationAction2"
+        layout="@layout/settings_notification_action"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/notificationAction1" />
 
-        <include
-            android:id="@+id/notificationAction3"
-            layout="@layout/settings_notification_action"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/notificationAction2" />
+    <include
+        android:id="@+id/notificationAction3"
+        layout="@layout/settings_notification_action"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/notificationAction2" />
 
-        <include
-            android:id="@+id/notificationAction4"
-            layout="@layout/settings_notification_action"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/notificationAction3" />
+    <include
+        android:id="@+id/notificationAction4"
+        layout="@layout/settings_notification_action"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/notificationAction3" />
 
-    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

From 30f0db1d28987805612325839432898d1ba77625 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 12:13:08 +0100
Subject: [PATCH 077/141] Customize only 2 notification actions on Android 13+

---
 .../custom/NotificationActionsPreference.java | 34 +++++++++++++++----
 .../main/res/layout/settings_notification.xml |  4 +--
 app/src/main/res/values/strings.xml           |  3 +-
 3 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
index 3e92f297e..29a7c49a0 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
@@ -6,6 +6,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.ColorStateList;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -35,6 +36,7 @@ import org.schabi.newpipe.util.ThemeHelper;
 import org.schabi.newpipe.views.FocusOverlayView;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.IntStream;
 
 public class NotificationActionsPreference extends Preference {
@@ -56,6 +58,11 @@ public class NotificationActionsPreference extends Preference {
     public void onBindViewHolder(@NonNull final PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            ((TextView) holder.itemView.findViewById(R.id.summary))
+                    .setText(R.string.notification_actions_summary_android13);
+        }
+
         holder.itemView.setClickable(false);
         setupActions(holder.itemView);
     }
@@ -137,11 +144,19 @@ public class NotificationActionsPreference extends Preference {
 
         NotificationSlot(final int actionIndex, final View parentView) {
             this.i = actionIndex;
-
+            selectedAction = Objects.requireNonNull(getSharedPreferences()).getInt(
+                    getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
+                    NotificationConstants.SLOT_DEFAULTS[i]);
             final View view = parentView.findViewById(SLOT_ITEMS[i]);
-            setupSelectedAction(view);
-            setupTitle(view);
-            setupCheckbox(view);
+
+            // only show the last two notification slots on Android 13+
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || i >= 3) {
+                setupSelectedAction(view);
+                setupTitle(view);
+                setupCheckbox(view);
+            } else {
+                view.setVisibility(View.GONE);
+            }
         }
 
         void setupTitle(final View view) {
@@ -153,6 +168,14 @@ public class NotificationActionsPreference extends Preference {
 
         void setupCheckbox(final View view) {
             final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                // there are no compact slots to customize on Android 33+
+                compactSlotCheckBox.setVisibility(View.GONE);
+                view.findViewById(R.id.notificationActionCheckBoxClickableArea)
+                        .setVisibility(View.GONE);
+                return;
+            }
+
             compactSlotCheckBox.setChecked(compactSlots.contains(i));
             view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener(
                     v -> {
@@ -174,9 +197,6 @@ public class NotificationActionsPreference extends Preference {
         void setupSelectedAction(final View view) {
             icon = view.findViewById(R.id.notificationActionIcon);
             summary = view.findViewById(R.id.notificationActionSummary);
-            selectedAction = getSharedPreferences().getInt(
-                    getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
-                    NotificationConstants.SLOT_DEFAULTS[i]);
             updateInfo();
         }
 
diff --git a/app/src/main/res/layout/settings_notification.xml b/app/src/main/res/layout/settings_notification.xml
index e5d73a4ae..b8195f18c 100644
--- a/app/src/main/res/layout/settings_notification.xml
+++ b/app/src/main/res/layout/settings_notification.xml
@@ -7,7 +7,7 @@
         android:paddingTop="16dp">
 
     <org.schabi.newpipe.views.NewPipeTextView
-        android:id="@+id/textView"
+        android:id="@+id/summary"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="16dp"
@@ -29,7 +29,7 @@
         android:layout_marginTop="8dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/textView" />
+        app:layout_constraintTop_toBottomOf="@+id/summary" />
 
     <include
         android:id="@+id/notificationAction1"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d94abfe70..055ef467d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -57,7 +57,8 @@
     <string name="notification_action_2_title">Third action button</string>
     <string name="notification_action_3_title">Fourth action button</string>
     <string name="notification_action_4_title">Fifth action button</string>
-    <string name="notification_actions_summary">Edit each notification action below by tapping on it. Select up to three of them to be shown in the compact notification by using the checkboxes on the right</string>
+    <string name="notification_actions_summary">Edit each notification action below by tapping on it. Select up to three of them to be shown in the compact notification by using the checkboxes on the right.</string>
+    <string name="notification_actions_summary_android13">Edit each notification action below by tapping on it. The first three actions (play/pause, previous and next) are set by the system and cannot be customized.</string>
     <string name="notification_actions_at_most_three">You can select at most three actions to show in the compact notification!</string>
     <string name="notification_action_repeat">Repeat</string>
     <string name="notification_action_shuffle">Shuffle</string>

From aab6580195c2448b12a72b439550bd2ad2a3ed42 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 12:31:59 +0100
Subject: [PATCH 078/141] Extract NotificationSlot from
 NotificationActionsPreference

---
 .../custom/NotificationActionsPreference.java | 187 ++----------------
 .../settings/custom/NotificationSlot.java     | 172 ++++++++++++++++
 2 files changed, 193 insertions(+), 166 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java

diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
index 29a7c49a0..43e9d6f0c 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
@@ -5,38 +5,22 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.res.ColorStateList;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.content.res.AppCompatResources;
-import androidx.core.widget.TextViewCompat;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import org.schabi.newpipe.App;
 import org.schabi.newpipe.R;
-import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
-import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
 import org.schabi.newpipe.player.notification.NotificationConstants;
-import org.schabi.newpipe.util.DeviceUtils;
-import org.schabi.newpipe.util.ThemeHelper;
-import org.schabi.newpipe.views.FocusOverlayView;
 
 import java.util.List;
-import java.util.Objects;
 import java.util.stream.IntStream;
 
 public class NotificationActionsPreference extends Preference {
@@ -47,8 +31,9 @@ public class NotificationActionsPreference extends Preference {
     }
 
 
-    @Nullable private NotificationSlot[] notificationSlots = null;
-    @Nullable private List<Integer> compactSlots = null;
+    private NotificationSlot[] notificationSlots;
+    private List<Integer> compactSlots;
+
 
     ////////////////////////////////////////////////////////////////////////////
     // Lifecycle
@@ -85,10 +70,26 @@ public class NotificationActionsPreference extends Preference {
         compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(),
                 getSharedPreferences(), 5);
         notificationSlots = IntStream.range(0, 5)
-                .mapToObj(i -> new NotificationSlot(i, view))
+                .mapToObj(i -> new NotificationSlot(getContext(), getSharedPreferences(), i, view,
+                        compactSlots.contains(i), this::onToggleCompactSlot))
                 .toArray(NotificationSlot[]::new);
     }
 
+    private void onToggleCompactSlot(final int i, final CheckBox checkBox) {
+        if (checkBox.isChecked()) {
+            compactSlots.remove((Integer) i);
+        } else if (compactSlots.size() < 3) {
+            compactSlots.add(i);
+        } else {
+            Toast.makeText(getContext(),
+                    R.string.notification_actions_at_most_three,
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        checkBox.toggle();
+    }
+
 
     ////////////////////////////////////////////////////////////////////////////
     // Saving
@@ -106,156 +107,10 @@ public class NotificationActionsPreference extends Preference {
 
             for (int i = 0; i < 5; i++) {
                 editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
-                        notificationSlots[i].selectedAction);
+                        notificationSlots[i].getSelectedAction());
             }
 
             editor.apply();
         }
     }
-
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Notification action
-    ////////////////////////////////////////////////////////////////////////////
-
-    private static final int[] SLOT_ITEMS = {
-            R.id.notificationAction0,
-            R.id.notificationAction1,
-            R.id.notificationAction2,
-            R.id.notificationAction3,
-            R.id.notificationAction4,
-    };
-
-    private static final int[] SLOT_TITLES = {
-            R.string.notification_action_0_title,
-            R.string.notification_action_1_title,
-            R.string.notification_action_2_title,
-            R.string.notification_action_3_title,
-            R.string.notification_action_4_title,
-    };
-
-    private class NotificationSlot {
-
-        final int i;
-        @NotificationConstants.Action int selectedAction;
-
-        ImageView icon;
-        TextView summary;
-
-        NotificationSlot(final int actionIndex, final View parentView) {
-            this.i = actionIndex;
-            selectedAction = Objects.requireNonNull(getSharedPreferences()).getInt(
-                    getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
-                    NotificationConstants.SLOT_DEFAULTS[i]);
-            final View view = parentView.findViewById(SLOT_ITEMS[i]);
-
-            // only show the last two notification slots on Android 13+
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || i >= 3) {
-                setupSelectedAction(view);
-                setupTitle(view);
-                setupCheckbox(view);
-            } else {
-                view.setVisibility(View.GONE);
-            }
-        }
-
-        void setupTitle(final View view) {
-            ((TextView) view.findViewById(R.id.notificationActionTitle))
-                    .setText(SLOT_TITLES[i]);
-            view.findViewById(R.id.notificationActionClickableArea).setOnClickListener(
-                    v -> openActionChooserDialog());
-        }
-
-        void setupCheckbox(final View view) {
-            final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-                // there are no compact slots to customize on Android 33+
-                compactSlotCheckBox.setVisibility(View.GONE);
-                view.findViewById(R.id.notificationActionCheckBoxClickableArea)
-                        .setVisibility(View.GONE);
-                return;
-            }
-
-            compactSlotCheckBox.setChecked(compactSlots.contains(i));
-            view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener(
-                    v -> {
-                        if (compactSlotCheckBox.isChecked()) {
-                            compactSlots.remove((Integer) i);
-                        } else if (compactSlots.size() < 3) {
-                            compactSlots.add(i);
-                        } else {
-                            Toast.makeText(getContext(),
-                                    R.string.notification_actions_at_most_three,
-                                    Toast.LENGTH_SHORT).show();
-                            return;
-                        }
-
-                        compactSlotCheckBox.toggle();
-                    });
-        }
-
-        void setupSelectedAction(final View view) {
-            icon = view.findViewById(R.id.notificationActionIcon);
-            summary = view.findViewById(R.id.notificationActionSummary);
-            updateInfo();
-        }
-
-        void updateInfo() {
-            if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) {
-                icon.setImageDrawable(null);
-            } else {
-                icon.setImageDrawable(AppCompatResources.getDrawable(getContext(),
-                        NotificationConstants.ACTION_ICONS[selectedAction]));
-            }
-
-            summary.setText(NotificationConstants.getActionName(getContext(), selectedAction));
-        }
-
-        void openActionChooserDialog() {
-            final LayoutInflater inflater = LayoutInflater.from(getContext());
-            final SingleChoiceDialogViewBinding binding =
-                    SingleChoiceDialogViewBinding.inflate(inflater);
-
-            final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
-                    .setTitle(SLOT_TITLES[i])
-                    .setView(binding.getRoot())
-                    .setCancelable(true)
-                    .create();
-
-            final View.OnClickListener radioButtonsClickListener = v -> {
-                selectedAction = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][v.getId()];
-                updateInfo();
-                alertDialog.dismiss();
-            };
-
-            for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
-                final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
-                final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
-                        .getRoot();
-
-                // if present set action icon with correct color
-                final int iconId = NotificationConstants.ACTION_ICONS[action];
-                if (iconId != 0) {
-                    radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconId, 0);
-
-                    final var color = ColorStateList.valueOf(ThemeHelper
-                            .resolveColorFromAttr(getContext(), android.R.attr.textColorPrimary));
-                    TextViewCompat.setCompoundDrawableTintList(radioButton, color);
-                }
-
-                radioButton.setText(NotificationConstants.getActionName(getContext(), action));
-                radioButton.setChecked(action == selectedAction);
-                radioButton.setId(id);
-                radioButton.setLayoutParams(new RadioGroup.LayoutParams(
-                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
-                radioButton.setOnClickListener(radioButtonsClickListener);
-                binding.list.addView(radioButton);
-            }
-            alertDialog.show();
-
-            if (DeviceUtils.isTv(getContext())) {
-                FocusOverlayView.setupFocusObserver(alertDialog);
-            }
-        }
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
new file mode 100644
index 000000000..412215d0f
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
@@ -0,0 +1,172 @@
+package org.schabi.newpipe.settings.custom;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.ColorStateList;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.widget.TextViewCompat;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
+import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
+import org.schabi.newpipe.player.notification.NotificationConstants;
+import org.schabi.newpipe.util.DeviceUtils;
+import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.views.FocusOverlayView;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+class NotificationSlot {
+
+    private static final int[] SLOT_ITEMS = {
+            R.id.notificationAction0,
+            R.id.notificationAction1,
+            R.id.notificationAction2,
+            R.id.notificationAction3,
+            R.id.notificationAction4,
+    };
+
+    private static final int[] SLOT_TITLES = {
+            R.string.notification_action_0_title,
+            R.string.notification_action_1_title,
+            R.string.notification_action_2_title,
+            R.string.notification_action_3_title,
+            R.string.notification_action_4_title,
+    };
+
+    private final int i;
+    private @NotificationConstants.Action int selectedAction;
+    private final Context context;
+    private final BiConsumer<Integer, CheckBox> onToggleCompactSlot;
+
+    private ImageView icon;
+    private TextView summary;
+
+    NotificationSlot(final Context context,
+                     final SharedPreferences prefs,
+                     final int actionIndex,
+                     final View parentView,
+                     final boolean isCompactSlotChecked,
+                     final BiConsumer<Integer, CheckBox> onToggleCompactSlot) {
+        this.context = context;
+        this.i = actionIndex;
+        this.onToggleCompactSlot = onToggleCompactSlot;
+
+        selectedAction = Objects.requireNonNull(prefs).getInt(
+                context.getString(NotificationConstants.SLOT_PREF_KEYS[i]),
+                NotificationConstants.SLOT_DEFAULTS[i]);
+        final View view = parentView.findViewById(SLOT_ITEMS[i]);
+
+        // only show the last two notification slots on Android 13+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || i >= 3) {
+            setupSelectedAction(view);
+            setupTitle(view);
+            setupCheckbox(view, isCompactSlotChecked);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    void setupTitle(final View view) {
+        ((TextView) view.findViewById(R.id.notificationActionTitle))
+                .setText(SLOT_TITLES[i]);
+        view.findViewById(R.id.notificationActionClickableArea).setOnClickListener(
+                v -> openActionChooserDialog());
+    }
+
+    void setupCheckbox(final View view, final boolean isCompactSlotChecked) {
+        final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            // there are no compact slots to customize on Android 33+
+            compactSlotCheckBox.setVisibility(View.GONE);
+            view.findViewById(R.id.notificationActionCheckBoxClickableArea)
+                    .setVisibility(View.GONE);
+            return;
+        }
+
+        compactSlotCheckBox.setChecked(isCompactSlotChecked);
+        view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener(
+                v -> onToggleCompactSlot.accept(i, compactSlotCheckBox));
+    }
+
+    void setupSelectedAction(final View view) {
+        icon = view.findViewById(R.id.notificationActionIcon);
+        summary = view.findViewById(R.id.notificationActionSummary);
+        updateInfo();
+    }
+
+    void updateInfo() {
+        if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) {
+            icon.setImageDrawable(null);
+        } else {
+            icon.setImageDrawable(AppCompatResources.getDrawable(context,
+                    NotificationConstants.ACTION_ICONS[selectedAction]));
+        }
+
+        summary.setText(NotificationConstants.getActionName(context, selectedAction));
+    }
+
+    void openActionChooserDialog() {
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final SingleChoiceDialogViewBinding binding =
+                SingleChoiceDialogViewBinding.inflate(inflater);
+
+        final AlertDialog alertDialog = new AlertDialog.Builder(context)
+                .setTitle(SLOT_TITLES[i])
+                .setView(binding.getRoot())
+                .setCancelable(true)
+                .create();
+
+        final View.OnClickListener radioButtonsClickListener = v -> {
+            selectedAction = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][v.getId()];
+            updateInfo();
+            alertDialog.dismiss();
+        };
+
+        for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
+            final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
+            final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
+                    .getRoot();
+
+            // if present set action icon with correct color
+            final int iconId = NotificationConstants.ACTION_ICONS[action];
+            if (iconId != 0) {
+                radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconId, 0);
+
+                final var color = ColorStateList.valueOf(ThemeHelper
+                        .resolveColorFromAttr(context, android.R.attr.textColorPrimary));
+                TextViewCompat.setCompoundDrawableTintList(radioButton, color);
+            }
+
+            radioButton.setText(NotificationConstants.getActionName(context, action));
+            radioButton.setChecked(action == selectedAction);
+            radioButton.setId(id);
+            radioButton.setLayoutParams(new RadioGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            radioButton.setOnClickListener(radioButtonsClickListener);
+            binding.list.addView(radioButton);
+        }
+        alertDialog.show();
+
+        if (DeviceUtils.isTv(context)) {
+            FocusOverlayView.setupFocusObserver(alertDialog);
+        }
+    }
+
+    @NotificationConstants.Action
+    public int getSelectedAction() {
+        return selectedAction;
+    }
+}

From 9fb8125655060a343eb0a68d9a3fe69b01a28b9d Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 14:04:18 +0100
Subject: [PATCH 079/141] Allow each notification slot to contain any possible
 action

---
 .../notification/NotificationConstants.java   | 37 +++++++---------
 .../player/notification/NotificationUtil.java | 43 ++++++++++++-------
 .../custom/NotificationActionsPreference.java |  5 ++-
 .../settings/custom/NotificationSlot.java     |  6 +--
 4 files changed, 48 insertions(+), 43 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java
index 89bf0b22a..b9607f7ea 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java
@@ -13,7 +13,7 @@ import org.schabi.newpipe.util.Localization;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -65,10 +65,16 @@ public final class NotificationConstants {
     public static final int CLOSE = 11;
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS, SMART_FORWARD_NEXT,
-            PLAY_PAUSE, PLAY_PAUSE_BUFFERING, REPEAT, SHUFFLE, CLOSE})
+    @IntDef({NOTHING, PREVIOUS, NEXT, REWIND, FORWARD,
+            SMART_REWIND_PREVIOUS, SMART_FORWARD_NEXT, PLAY_PAUSE, PLAY_PAUSE_BUFFERING, REPEAT,
+            SHUFFLE, CLOSE})
     public @interface Action { }
 
+    @Action
+    public static final int[] ALL_ACTIONS = {NOTHING, PREVIOUS, NEXT, REWIND, FORWARD,
+            SMART_REWIND_PREVIOUS, SMART_FORWARD_NEXT, PLAY_PAUSE, PLAY_PAUSE_BUFFERING, REPEAT,
+            SHUFFLE, CLOSE};
+
     @DrawableRes
     public static final int[] ACTION_ICONS = {
             0,
@@ -95,16 +101,6 @@ public final class NotificationConstants {
             CLOSE,
     };
 
-    @Action
-    public static final int[][] SLOT_ALLOWED_ACTIONS = {
-            new int[] {PREVIOUS, REWIND, SMART_REWIND_PREVIOUS},
-            new int[] {REWIND, PLAY_PAUSE, PLAY_PAUSE_BUFFERING},
-            new int[] {NEXT, FORWARD, SMART_FORWARD_NEXT, PLAY_PAUSE, PLAY_PAUSE_BUFFERING},
-            new int[] {NOTHING, PREVIOUS, NEXT, REWIND, FORWARD, SMART_REWIND_PREVIOUS,
-                    SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE},
-            new int[] {NOTHING, NEXT, FORWARD, SMART_FORWARD_NEXT, REPEAT, SHUFFLE, CLOSE},
-    };
-
     public static final int[] SLOT_PREF_KEYS = {
             R.string.notification_slot_0_key,
             R.string.notification_slot_1_key,
@@ -165,14 +161,11 @@ public final class NotificationConstants {
     /**
      * @param context the context to use
      * @param sharedPreferences the shared preferences to query values from
-     * @param slotCount remove indices >= than this value (set to {@code 5} to do nothing, or make
-     *                  it lower if there are slots with empty actions)
      * @return a sorted list of the indices of the slots to use as compact slots
      */
-    public static List<Integer> getCompactSlotsFromPreferences(
+    public static Collection<Integer> getCompactSlotsFromPreferences(
             @NonNull final Context context,
-            final SharedPreferences sharedPreferences,
-            final int slotCount) {
+            final SharedPreferences sharedPreferences) {
         final SortedSet<Integer> compactSlots = new TreeSet<>();
         for (int i = 0; i < 3; i++) {
             final int compactSlot = sharedPreferences.getInt(
@@ -180,14 +173,14 @@ public final class NotificationConstants {
 
             if (compactSlot == Integer.MAX_VALUE) {
                 // settings not yet populated, return default values
-                return new ArrayList<>(SLOT_COMPACT_DEFAULTS);
+                return SLOT_COMPACT_DEFAULTS;
             }
 
-            // a negative value (-1) is set when the user does not want a particular compact slot
-            if (compactSlot >= 0 && compactSlot < slotCount) {
+            if (compactSlot >= 0) {
+                // compact slot is < 0 if there are less than 3 checked checkboxes
                 compactSlots.add(compactSlot);
             }
         }
-        return new ArrayList<>(compactSlots);
+        return compactSlots;
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
index 3fa7c2623..551e2b863 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
@@ -23,6 +23,8 @@ import org.schabi.newpipe.player.Player;
 import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
 import org.schabi.newpipe.util.NavigationHelper;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -101,21 +103,7 @@ public final class NotificationUtil {
                 new NotificationCompat.Builder(player.getContext(),
                 player.getContext().getString(R.string.notification_channel_id));
 
-        initializeNotificationSlots();
-
-        // count the number of real slots, to make sure compact slots indices are not out of bound
-        int nonNothingSlotCount = 5;
-        if (notificationSlots[3] == NotificationConstants.NOTHING) {
-            --nonNothingSlotCount;
-        }
-        if (notificationSlots[4] == NotificationConstants.NOTHING) {
-            --nonNothingSlotCount;
-        }
-
-        // build the compact slot indices array (need code to convert from Integer... because Java)
-        final List<Integer> compactSlotList = NotificationConstants.getCompactSlotsFromPreferences(
-                player.getContext(), player.getPrefs(), nonNothingSlotCount);
-        final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray();
+        final int[] compactSlots = initializeNotificationSlots();
 
         final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots);
         player.UIs()
@@ -209,12 +197,35 @@ public final class NotificationUtil {
     // ACTIONS
     /////////////////////////////////////////////////////
 
-    private void initializeNotificationSlots() {
+    /**
+     * The compact slots array from settings contains indices from 0 to 4, each referring to one of
+     * the five actions configurable by the user. However, if the user sets an action to "Nothing",
+     * then all of the actions coming after will have a "settings index" different than the index
+     * of the corresponding action when sent to the system.
+     *
+     * @return the indices of compact slots referred to the list of non-nothing actions that will be
+     *         sent to the system
+     */
+    private int[] initializeNotificationSlots() {
+        final Collection<Integer> settingsCompactSlots = NotificationConstants
+                .getCompactSlotsFromPreferences(player.getContext(), player.getPrefs());
+        final List<Integer> adjustedCompactSlots = new ArrayList<>();
+
+        int nonNothingIndex = 0;
         for (int i = 0; i < 5; ++i) {
             notificationSlots[i] = player.getPrefs().getInt(
                     player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
                     NotificationConstants.SLOT_DEFAULTS[i]);
+
+            if (notificationSlots[i] != NotificationConstants.NOTHING) {
+                if (settingsCompactSlots.contains(i)) {
+                    adjustedCompactSlots.add(nonNothingIndex);
+                }
+                nonNothingIndex += 1;
+            }
         }
+
+        return adjustedCompactSlots.stream().mapToInt(Integer::intValue).toArray();
     }
 
     @SuppressLint("RestrictedApi")
diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
index 43e9d6f0c..7dfddef20 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java
@@ -20,6 +20,7 @@ import org.schabi.newpipe.App;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.player.notification.NotificationConstants;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.IntStream;
 
@@ -67,8 +68,8 @@ public class NotificationActionsPreference extends Preference {
     ////////////////////////////////////////////////////////////////////////////
 
     private void setupActions(@NonNull final View view) {
-        compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(),
-                getSharedPreferences(), 5);
+        compactSlots = new ArrayList<>(NotificationConstants.getCompactSlotsFromPreferences(
+                getContext(), getSharedPreferences()));
         notificationSlots = IntStream.range(0, 5)
                 .mapToObj(i -> new NotificationSlot(getContext(), getSharedPreferences(), i, view,
                         compactSlots.contains(i), this::onToggleCompactSlot))
diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
index 412215d0f..074532876 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
@@ -130,13 +130,13 @@ class NotificationSlot {
                 .create();
 
         final View.OnClickListener radioButtonsClickListener = v -> {
-            selectedAction = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][v.getId()];
+            selectedAction = NotificationConstants.ALL_ACTIONS[v.getId()];
             updateInfo();
             alertDialog.dismiss();
         };
 
-        for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
-            final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
+        for (int id = 0; id < NotificationConstants.ALL_ACTIONS.length; ++id) {
+            final int action = NotificationConstants.ALL_ACTIONS[id];
             final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
                     .getRoot();
 

From 2c4c28309900276bfa5e36a3cebbf0a16730a1a9 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 16 Nov 2023 09:47:27 +0100
Subject: [PATCH 080/141] Extract NotificationActionData from NotificationUtil

---
 .../notification/NotificationActionData.java  | 168 ++++++++++++++++++
 .../player/notification/NotificationUtil.java | 136 ++------------
 2 files changed, 184 insertions(+), 120 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java

diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
new file mode 100644
index 000000000..fd5e03bf1
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
@@ -0,0 +1,168 @@
+package org.schabi.newpipe.player.notification;
+
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
+import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
+
+import android.content.Context;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.player.Player;
+
+public final class NotificationActionData {
+    @Nullable
+    private final String action;
+    @NonNull
+    private final String name;
+    @DrawableRes
+    private final int icon;
+
+    public NotificationActionData(@Nullable final String action, @NonNull final String name,
+                                  @DrawableRes final int icon) {
+        this.action = action;
+        this.name = name;
+        this.icon = icon;
+    }
+
+    @Nullable
+    public String action() {
+        return action;
+    }
+
+    @NonNull
+    public String name() {
+        return name;
+    }
+
+    @DrawableRes
+    public int icon() {
+        return icon;
+    }
+
+
+    @Nullable
+    public static NotificationActionData fromNotificationActionEnum(
+            @NonNull final Player player,
+            @NotificationConstants.Action final int selectedAction
+    ) {
+
+        final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction];
+        final Context ctx = player.getContext();
+
+        switch (selectedAction) {
+            case NotificationConstants.PREVIOUS:
+                return new NotificationActionData(ACTION_PLAY_PREVIOUS,
+                        ctx.getString(R.string.exo_controls_previous_description), baseActionIcon);
+
+            case NotificationConstants.NEXT:
+                return new NotificationActionData(ACTION_PLAY_NEXT,
+                        ctx.getString(R.string.exo_controls_next_description), baseActionIcon);
+
+            case NotificationConstants.REWIND:
+                return new NotificationActionData(ACTION_FAST_REWIND,
+                        ctx.getString(R.string.exo_controls_rewind_description), baseActionIcon);
+
+            case NotificationConstants.FORWARD:
+                return new NotificationActionData(ACTION_FAST_FORWARD,
+                        ctx.getString(R.string.exo_controls_fastforward_description),
+                        baseActionIcon);
+
+            case NotificationConstants.SMART_REWIND_PREVIOUS:
+                if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
+                    return new NotificationActionData(ACTION_PLAY_PREVIOUS,
+                            ctx.getString(R.string.exo_controls_previous_description),
+                            R.drawable.exo_notification_previous);
+                } else {
+                    return new NotificationActionData(ACTION_FAST_REWIND,
+                            ctx.getString(R.string.exo_controls_rewind_description),
+                            R.drawable.exo_controls_rewind);
+                }
+
+            case NotificationConstants.SMART_FORWARD_NEXT:
+                if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
+                    return new NotificationActionData(ACTION_PLAY_NEXT,
+                            ctx.getString(R.string.exo_controls_next_description),
+                            R.drawable.exo_notification_next);
+                } else {
+                    return new NotificationActionData(ACTION_FAST_FORWARD,
+                            ctx.getString(R.string.exo_controls_fastforward_description),
+                            R.drawable.exo_controls_fastforward);
+                }
+
+            case NotificationConstants.PLAY_PAUSE_BUFFERING:
+                if (player.getCurrentState() == Player.STATE_PREFLIGHT
+                        || player.getCurrentState() == Player.STATE_BLOCKED
+                        || player.getCurrentState() == Player.STATE_BUFFERING) {
+                    // null intent action -> show hourglass icon that does nothing when clicked
+                    return new NotificationActionData(null,
+                            ctx.getString(R.string.notification_action_buffering),
+                            R.drawable.ic_hourglass_top);
+                }
+
+                // fallthrough
+            case NotificationConstants.PLAY_PAUSE:
+                if (player.getCurrentState() == Player.STATE_COMPLETED) {
+                    return new NotificationActionData(ACTION_PLAY_PAUSE,
+                            ctx.getString(R.string.exo_controls_pause_description),
+                            R.drawable.ic_replay);
+                } else if (player.isPlaying()
+                        || player.getCurrentState() == Player.STATE_PREFLIGHT
+                        || player.getCurrentState() == Player.STATE_BLOCKED
+                        || player.getCurrentState() == Player.STATE_BUFFERING) {
+                    return new NotificationActionData(ACTION_PLAY_PAUSE,
+                            ctx.getString(R.string.exo_controls_pause_description),
+                            R.drawable.exo_notification_pause);
+                } else {
+                    return new NotificationActionData(ACTION_PLAY_PAUSE,
+                            ctx.getString(R.string.exo_controls_play_description),
+                            R.drawable.exo_notification_play);
+                }
+
+            case NotificationConstants.REPEAT:
+                if (player.getRepeatMode() == REPEAT_MODE_ALL) {
+                    return new NotificationActionData(ACTION_REPEAT,
+                            ctx.getString(R.string.exo_controls_repeat_all_description),
+                            R.drawable.exo_media_action_repeat_all);
+                } else if (player.getRepeatMode() == REPEAT_MODE_ONE) {
+                    return new NotificationActionData(ACTION_REPEAT,
+                            ctx.getString(R.string.exo_controls_repeat_one_description),
+                            R.drawable.exo_media_action_repeat_one);
+                } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ {
+                    return new NotificationActionData(ACTION_REPEAT,
+                            ctx.getString(R.string.exo_controls_repeat_off_description),
+                            R.drawable.exo_media_action_repeat_off);
+                }
+
+            case NotificationConstants.SHUFFLE:
+                if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) {
+                    return new NotificationActionData(ACTION_SHUFFLE,
+                            ctx.getString(R.string.exo_controls_shuffle_on_description),
+                            R.drawable.exo_controls_shuffle_on);
+                } else {
+                    return new NotificationActionData(ACTION_SHUFFLE,
+                            ctx.getString(R.string.exo_controls_shuffle_off_description),
+                            R.drawable.exo_controls_shuffle_off);
+                }
+
+            case NotificationConstants.CLOSE:
+                return new NotificationActionData(ACTION_CLOSE, ctx.getString(R.string.close),
+                        R.drawable.ic_close);
+
+            case NotificationConstants.NOTHING:
+            default:
+                // do nothing
+                return null;
+        }
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
index 551e2b863..72b979f9d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
@@ -1,16 +1,19 @@
 package org.schabi.newpipe.player.notification;
 
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+import static androidx.media.app.NotificationCompat.MediaStyle;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE;
+
 import android.annotation.SuppressLint;
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.pm.ServiceInfo;
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.util.Log;
 
-import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
 import androidx.core.app.NotificationCompat;
 import androidx.core.app.NotificationManagerCompat;
 import androidx.core.app.PendingIntentCompat;
@@ -29,19 +32,6 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-import static androidx.media.app.NotificationCompat.MediaStyle;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT;
-import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
-
 /**
  * This is a utility class for player notifications.
  */
@@ -238,115 +228,21 @@ public final class NotificationUtil {
 
     private void addAction(final NotificationCompat.Builder builder,
                            @NotificationConstants.Action final int slot) {
-        final NotificationCompat.Action action = getAction(slot);
-        if (action != null) {
-            builder.addAction(action);
+        @Nullable final NotificationActionData data =
+                NotificationActionData.fromNotificationActionEnum(player, slot);
+        if (data == null) {
+            return;
         }
-    }
 
-    @Nullable
-    private NotificationCompat.Action getAction(
-            @NotificationConstants.Action final int selectedAction) {
-        final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction];
-        switch (selectedAction) {
-            case NotificationConstants.PREVIOUS:
-                return getAction(baseActionIcon,
-                        R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
-
-            case NotificationConstants.NEXT:
-                return getAction(baseActionIcon,
-                        R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
-
-            case NotificationConstants.REWIND:
-                return getAction(baseActionIcon,
-                        R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
-
-            case NotificationConstants.FORWARD:
-                return getAction(baseActionIcon,
-                        R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
-
-            case NotificationConstants.SMART_REWIND_PREVIOUS:
-                if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
-                    return getAction(R.drawable.exo_notification_previous,
-                            R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
-                } else {
-                    return getAction(R.drawable.exo_controls_rewind,
-                            R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
-                }
-
-            case NotificationConstants.SMART_FORWARD_NEXT:
-                if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
-                    return getAction(R.drawable.exo_notification_next,
-                            R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
-                } else {
-                    return getAction(R.drawable.exo_controls_fastforward,
-                            R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
-                }
-
-            case NotificationConstants.PLAY_PAUSE_BUFFERING:
-                if (player.getCurrentState() == Player.STATE_PREFLIGHT
-                        || player.getCurrentState() == Player.STATE_BLOCKED
-                        || player.getCurrentState() == Player.STATE_BUFFERING) {
-                    // null intent -> show hourglass icon that does nothing when clicked
-                    return new NotificationCompat.Action(R.drawable.ic_hourglass_top,
-                            player.getContext().getString(R.string.notification_action_buffering),
-                            null);
-                }
-
-                // fallthrough
-            case NotificationConstants.PLAY_PAUSE:
-                if (player.getCurrentState() == Player.STATE_COMPLETED) {
-                    return getAction(R.drawable.ic_replay,
-                            R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
-                } else if (player.isPlaying()
-                        || player.getCurrentState() == Player.STATE_PREFLIGHT
-                        || player.getCurrentState() == Player.STATE_BLOCKED
-                        || player.getCurrentState() == Player.STATE_BUFFERING) {
-                    return getAction(R.drawable.exo_notification_pause,
-                            R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
-                } else {
-                    return getAction(R.drawable.exo_notification_play,
-                            R.string.exo_controls_play_description, ACTION_PLAY_PAUSE);
-                }
-
-            case NotificationConstants.REPEAT:
-                if (player.getRepeatMode() == REPEAT_MODE_ALL) {
-                    return getAction(R.drawable.exo_media_action_repeat_all,
-                            R.string.exo_controls_repeat_all_description, ACTION_REPEAT);
-                } else if (player.getRepeatMode() == REPEAT_MODE_ONE) {
-                    return getAction(R.drawable.exo_media_action_repeat_one,
-                            R.string.exo_controls_repeat_one_description, ACTION_REPEAT);
-                } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ {
-                    return getAction(R.drawable.exo_media_action_repeat_off,
-                            R.string.exo_controls_repeat_off_description, ACTION_REPEAT);
-                }
-
-            case NotificationConstants.SHUFFLE:
-                if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) {
-                    return getAction(R.drawable.exo_controls_shuffle_on,
-                            R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE);
-                } else {
-                    return getAction(R.drawable.exo_controls_shuffle_off,
-                            R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE);
-                }
-
-            case NotificationConstants.CLOSE:
-                return getAction(R.drawable.ic_close,
-                        R.string.close, ACTION_CLOSE);
-
-            case NotificationConstants.NOTHING:
-            default:
-                // do nothing
-                return null;
+        final PendingIntent intent;
+        if (data.action() == null) {
+            intent = null;
+        } else {
+            intent = PendingIntentCompat.getBroadcast(player.getContext(), NOTIFICATION_ID,
+                    new Intent(data.action()), FLAG_UPDATE_CURRENT, false);
         }
-    }
 
-    private NotificationCompat.Action getAction(@DrawableRes final int drawable,
-                                                @StringRes final int title,
-                                                final String intentAction) {
-        return new NotificationCompat.Action(drawable, player.getContext().getString(title),
-                PendingIntentCompat.getBroadcast(player.getContext(), NOTIFICATION_ID,
-                        new Intent(intentAction), FLAG_UPDATE_CURRENT, false));
+        builder.addAction(new NotificationCompat.Action(data.icon(), data.name(), intent));
     }
 
     private Intent getIntentForNotification() {

From 5edafca05ae485940094fb940a4fbac2886d4bd9 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 16 Nov 2023 11:04:01 +0100
Subject: [PATCH 081/141] Implement notification actions via
 MediaSessionConnector on Android 13+

---
 .../mediasession/MediaSessionPlayerUi.java    | 111 ++++++++++++++++++
 .../SessionConnectorActionProvider.java       |  51 ++++++++
 .../player/notification/NotificationUtil.java |  18 ++-
 3 files changed, 176 insertions(+), 4 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java

diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
index 6f76a91d1..53d6c297a 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
@@ -1,10 +1,12 @@
 package org.schabi.newpipe.player.mediasession;
 
 import static org.schabi.newpipe.MainActivity.DEBUG;
+import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION;
 
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
+import android.os.Build;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.util.Log;
@@ -14,14 +16,20 @@ import androidx.annotation.Nullable;
 import androidx.media.session.MediaButtonReceiver;
 
 import com.google.android.exoplayer2.ForwardingPlayer;
+import com.google.android.exoplayer2.Player.RepeatMode;
 import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
 
 import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
 import org.schabi.newpipe.player.Player;
+import org.schabi.newpipe.player.notification.NotificationActionData;
+import org.schabi.newpipe.player.notification.NotificationConstants;
 import org.schabi.newpipe.player.ui.PlayerUi;
 import org.schabi.newpipe.player.ui.VideoPlayerUi;
 import org.schabi.newpipe.util.StreamTypeUtil;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 
 public class MediaSessionPlayerUi extends PlayerUi
@@ -163,4 +171,107 @@ public class MediaSessionPlayerUi extends PlayerUi
 
         return builder.build();
     }
+
+
+    private void updateMediaSessionActions() {
+        // On Android 13+ (or Android T or API 33+) the actions in the player notification can't be
+        // controlled directly anymore, but are instead derived from custom media session actions.
+        // However the system allows customizing only two of these actions, since the other three
+        // are fixed to play-pause-buffering, previous, next.
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            // Although setting media session actions on older android versions doesn't seem to
+            // cause any trouble, it also doesn't seem to do anything, so we don't do anything to
+            // save battery. Check out NotificationUtil.updateActions() to see what happens on
+            // older android versions.
+            return;
+        }
+
+        final List<SessionConnectorActionProvider> actions = new ArrayList<>(2);
+        for (int i = 3; i < 5; ++i) {
+            // only use the fourth and fifth actions (the settings page also shows only the last 2)
+            final int action = player.getPrefs().getInt(
+                    player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
+                    NotificationConstants.SLOT_DEFAULTS[i]);
+
+            @Nullable final NotificationActionData data =
+                    NotificationActionData.fromNotificationActionEnum(player, action);
+
+            if (data != null) {
+                actions.add(new SessionConnectorActionProvider(data, context));
+            }
+        }
+
+        sessionConnector.setCustomActionProviders(
+                actions.toArray(new MediaSessionConnector.CustomActionProvider[0]));
+    }
+
+    @Override
+    public void onBlocked() {
+        super.onBlocked();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onPlaying() {
+        super.onPlaying();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onBuffering() {
+        super.onBuffering();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onPaused() {
+        super.onPaused();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onPausedSeek() {
+        super.onPausedSeek();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onCompleted() {
+        super.onCompleted();
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
+        super.onRepeatModeChanged(repeatMode);
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) {
+        super.onShuffleModeEnabledChanged(shuffleModeEnabled);
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onBroadcastReceived(final Intent intent) {
+        super.onBroadcastReceived(intent);
+        if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) {
+            // the notification actions changed
+            updateMediaSessionActions();
+        }
+    }
+
+    @Override
+    public void onMetadataChanged(@NonNull final StreamInfo info) {
+        super.onMetadataChanged(info);
+        updateMediaSessionActions();
+    }
+
+    @Override
+    public void onPlayQueueEdited() {
+        super.onPlayQueueEdited();
+        updateMediaSessionActions();
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java
new file mode 100644
index 000000000..7b109c149
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java
@@ -0,0 +1,51 @@
+package org.schabi.newpipe.player.mediasession;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
+
+import org.schabi.newpipe.player.notification.NotificationActionData;
+
+import java.lang.ref.WeakReference;
+
+public class SessionConnectorActionProvider implements MediaSessionConnector.CustomActionProvider {
+
+    private final NotificationActionData data;
+    @NonNull
+    private final WeakReference<Context> context;
+
+    public SessionConnectorActionProvider(final NotificationActionData notificationActionData,
+                                          @NonNull final Context context) {
+        this.data = notificationActionData;
+        this.context = new WeakReference<>(context);
+    }
+
+    @Override
+    public void onCustomAction(@NonNull final Player player,
+                               @NonNull final String action,
+                               @Nullable final Bundle extras) {
+        final Context actualContext = context.get();
+        if (actualContext != null) {
+            actualContext.sendBroadcast(new Intent(action));
+        }
+    }
+
+    @Nullable
+    @Override
+    public PlaybackStateCompat.CustomAction getCustomAction(@NonNull final Player player) {
+        if (data.action() == null) {
+            return null;
+        } else {
+            return new PlaybackStateCompat.CustomAction.Builder(
+                    data.action(), data.name(), data.icon()
+            ).build();
+        }
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
index 72b979f9d..b3cfed1ce 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
@@ -92,15 +92,21 @@ public final class NotificationUtil {
         final NotificationCompat.Builder builder =
                 new NotificationCompat.Builder(player.getContext(),
                 player.getContext().getString(R.string.notification_channel_id));
+        final MediaStyle mediaStyle = new MediaStyle();
 
-        final int[] compactSlots = initializeNotificationSlots();
-
-        final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots);
+        // setup media style (compact notification slots and media session)
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            // notification actions are ignored on Android 13+, and are replaced by code in
+            // MediaSessionPlayerUi
+            final int[] compactSlots = initializeNotificationSlots();
+            mediaStyle.setShowActionsInCompactView(compactSlots);
+        }
         player.UIs()
                 .get(MediaSessionPlayerUi.class)
                 .flatMap(MediaSessionPlayerUi::getSessionToken)
                 .ifPresent(mediaStyle::setMediaSession);
 
+        // setup notification builder
         builder.setStyle(mediaStyle)
                 .setPriority(NotificationCompat.PRIORITY_HIGH)
                 .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
@@ -135,7 +141,11 @@ public final class NotificationUtil {
         notificationBuilder.setContentText(player.getUploaderName());
         notificationBuilder.setTicker(player.getVideoTitle());
 
-        updateActions(notificationBuilder);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            // notification actions are ignored on Android 13+, and are replaced by code in
+            // MediaSessionPlayerUi
+            updateActions(notificationBuilder);
+        }
     }
 
 

From 17e88f17498234f50ea58ba22bc8c6ab72d6d673 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 15:08:44 +0100
Subject: [PATCH 082/141] Do not update notification actions if nothing changed

This should avoid costly updates of the media session.
---
 .../mediasession/MediaSessionPlayerUi.java    | 45 ++++++++++++-------
 .../notification/NotificationActionData.java  | 17 ++++++-
 2 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
index 53d6c297a..737ebc5dd 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java
@@ -28,9 +28,11 @@ import org.schabi.newpipe.player.ui.PlayerUi;
 import org.schabi.newpipe.player.ui.VideoPlayerUi;
 import org.schabi.newpipe.util.StreamTypeUtil;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 public class MediaSessionPlayerUi extends PlayerUi
         implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -42,6 +44,10 @@ public class MediaSessionPlayerUi extends PlayerUi
     private final String ignoreHardwareMediaButtonsKey;
     private boolean shouldIgnoreHardwareMediaButtons = false;
 
+    // used to check whether any notification action changed, before sending costly updates
+    private List<NotificationActionData> prevNotificationActions = List.of();
+
+
     public MediaSessionPlayerUi(@NonNull final Player player) {
         super(player);
         ignoreHardwareMediaButtonsKey =
@@ -71,6 +77,10 @@ public class MediaSessionPlayerUi extends PlayerUi
 
         sessionConnector.setMetadataDeduplicationEnabled(true);
         sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata());
+
+        // force updating media session actions by resetting the previous ones
+        prevNotificationActions = List.of();
+        updateMediaSessionActions();
     }
 
     @Override
@@ -88,6 +98,7 @@ public class MediaSessionPlayerUi extends PlayerUi
             mediaSession.release();
             mediaSession = null;
         }
+        prevNotificationActions = List.of();
     }
 
     @Override
@@ -187,23 +198,25 @@ public class MediaSessionPlayerUi extends PlayerUi
             return;
         }
 
-        final List<SessionConnectorActionProvider> actions = new ArrayList<>(2);
-        for (int i = 3; i < 5; ++i) {
-            // only use the fourth and fifth actions (the settings page also shows only the last 2)
-            final int action = player.getPrefs().getInt(
-                    player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
-                    NotificationConstants.SLOT_DEFAULTS[i]);
+        // only use the fourth and fifth actions (the settings page also shows only the last 2 on
+        // Android 13+)
+        final List<NotificationActionData> newNotificationActions = IntStream.of(3, 4)
+                .map(i -> player.getPrefs().getInt(
+                        player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
+                        NotificationConstants.SLOT_DEFAULTS[i]))
+                .mapToObj(action -> NotificationActionData
+                        .fromNotificationActionEnum(player, action))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
 
-            @Nullable final NotificationActionData data =
-                    NotificationActionData.fromNotificationActionEnum(player, action);
-
-            if (data != null) {
-                actions.add(new SessionConnectorActionProvider(data, context));
-            }
+        // avoid costly notification actions update, if nothing changed from last time
+        if (!newNotificationActions.equals(prevNotificationActions)) {
+            prevNotificationActions = newNotificationActions;
+            sessionConnector.setCustomActionProviders(
+                    newNotificationActions.stream()
+                            .map(data -> new SessionConnectorActionProvider(data, context))
+                            .toArray(SessionConnectorActionProvider[]::new));
         }
-
-        sessionConnector.setCustomActionProviders(
-                actions.toArray(new MediaSessionConnector.CustomActionProvider[0]));
     }
 
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
index fd5e03bf1..98ee3d7b8 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
@@ -20,6 +20,8 @@ import androidx.annotation.Nullable;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.player.Player;
 
+import java.util.Objects;
+
 public final class NotificationActionData {
     @Nullable
     private final String action;
@@ -50,7 +52,6 @@ public final class NotificationActionData {
         return icon;
     }
 
-
     @Nullable
     public static NotificationActionData fromNotificationActionEnum(
             @NonNull final Player player,
@@ -165,4 +166,18 @@ public final class NotificationActionData {
                 return null;
         }
     }
+
+
+    @Override
+    public boolean equals(@Nullable final Object obj) {
+        return (obj instanceof NotificationActionData other)
+                && Objects.equals(this.action, other.action)
+                && this.name.equals(other.name)
+                && this.icon == other.icon;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(action, name, icon);
+    }
 }

From 4b1824e8c16aed9725ba970ca00b8cae37171cf6 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Dec 2023 15:13:18 +0100
Subject: [PATCH 083/141] Allow play/pausing from notification when buffering

This change is in line with a recent change in how the play/pause button behaves in the player ui: if the buffering indicator is shown, it's still possible to toggle play/pause, to allow e.g. pausing videos before they even start.
This change was needed because on Android 13+ notification actions can't be null, and thus the buffering hourglass action wasn't shown.
---
 .../SessionConnectorActionProvider.java          | 10 +++-------
 .../notification/NotificationActionData.java     | 16 ++++++++++------
 .../player/notification/NotificationUtil.java    | 10 ++--------
 3 files changed, 15 insertions(+), 21 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java
index 7b109c149..a5c9fccc9 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/SessionConnectorActionProvider.java
@@ -40,12 +40,8 @@ public class SessionConnectorActionProvider implements MediaSessionConnector.Cus
     @Nullable
     @Override
     public PlaybackStateCompat.CustomAction getCustomAction(@NonNull final Player player) {
-        if (data.action() == null) {
-            return null;
-        } else {
-            return new PlaybackStateCompat.CustomAction.Builder(
-                    data.action(), data.name(), data.icon()
-            ).build();
-        }
+        return new PlaybackStateCompat.CustomAction.Builder(
+                data.action(), data.name(), data.icon()
+        ).build();
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
index 98ee3d7b8..b3abcd0b5 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationActionData.java
@@ -11,6 +11,7 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
 import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT;
 import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 
 import androidx.annotation.DrawableRes;
@@ -23,21 +24,23 @@ import org.schabi.newpipe.player.Player;
 import java.util.Objects;
 
 public final class NotificationActionData {
-    @Nullable
+
+    @NonNull
     private final String action;
     @NonNull
     private final String name;
     @DrawableRes
     private final int icon;
 
-    public NotificationActionData(@Nullable final String action, @NonNull final String name,
+
+    public NotificationActionData(@NonNull final String action, @NonNull final String name,
                                   @DrawableRes final int icon) {
         this.action = action;
         this.name = name;
         this.icon = icon;
     }
 
-    @Nullable
+    @NonNull
     public String action() {
         return action;
     }
@@ -52,6 +55,8 @@ public final class NotificationActionData {
         return icon;
     }
 
+
+    @SuppressLint("PrivateResource") // we currently use Exoplayer's internal strings and icons
     @Nullable
     public static NotificationActionData fromNotificationActionEnum(
             @NonNull final Player player,
@@ -105,8 +110,7 @@ public final class NotificationActionData {
                 if (player.getCurrentState() == Player.STATE_PREFLIGHT
                         || player.getCurrentState() == Player.STATE_BLOCKED
                         || player.getCurrentState() == Player.STATE_BUFFERING) {
-                    // null intent action -> show hourglass icon that does nothing when clicked
-                    return new NotificationActionData(null,
+                    return new NotificationActionData(ACTION_PLAY_PAUSE,
                             ctx.getString(R.string.notification_action_buffering),
                             R.drawable.ic_hourglass_top);
                 }
@@ -171,7 +175,7 @@ public final class NotificationActionData {
     @Override
     public boolean equals(@Nullable final Object obj) {
         return (obj instanceof NotificationActionData other)
-                && Objects.equals(this.action, other.action)
+                && this.action.equals(other.action)
                 && this.name.equals(other.name)
                 && this.icon == other.icon;
     }
diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
index b3cfed1ce..30420b0c7 100644
--- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java
@@ -244,14 +244,8 @@ public final class NotificationUtil {
             return;
         }
 
-        final PendingIntent intent;
-        if (data.action() == null) {
-            intent = null;
-        } else {
-            intent = PendingIntentCompat.getBroadcast(player.getContext(), NOTIFICATION_ID,
-                    new Intent(data.action()), FLAG_UPDATE_CURRENT, false);
-        }
-
+        final PendingIntent intent = PendingIntentCompat.getBroadcast(player.getContext(),
+                NOTIFICATION_ID, new Intent(data.action()), FLAG_UPDATE_CURRENT, false);
         builder.addAction(new NotificationCompat.Action(data.icon(), data.name(), intent));
     }
 

From f98548698af0e4d5a58c99a187b35c3e92d10119 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Dec 2023 21:55:32 +0100
Subject: [PATCH 084/141] Android 33 -> Android 13

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
---
 .../org/schabi/newpipe/settings/custom/NotificationSlot.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
index 074532876..981ba3e75 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationSlot.java
@@ -89,7 +89,7 @@ class NotificationSlot {
     void setupCheckbox(final View view, final boolean isCompactSlotChecked) {
         final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-            // there are no compact slots to customize on Android 33+
+            // there are no compact slots to customize on Android 13+
             compactSlotCheckBox.setVisibility(View.GONE);
             view.findViewById(R.id.notificationActionCheckBoxClickableArea)
                     .setVisibility(View.GONE);

From 04bdc1cc0bcb178be478ad1d60c1f5513d5c9bad Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Dec 2023 23:46:16 +0100
Subject: [PATCH 085/141] Base cache key on info type instead of item type

It didn't really made sense to consider two cache keys as equal based on the type of items contained within that list.
---
 .../fragments/detail/VideoDetailFragment.java |  8 +--
 .../schabi/newpipe/util/ExtractorHelper.java  | 58 +++++++++++--------
 .../org/schabi/newpipe/util/InfoCache.java    | 42 +++++++++-----
 3 files changed, 68 insertions(+), 40 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 4da0a561e..bcdeb7ef7 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -72,7 +72,6 @@ import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.error.ReCaptchaActivity;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.Image;
-import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.NewPipe;
 import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
 import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
@@ -107,16 +106,17 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi;
 import org.schabi.newpipe.util.Constants;
 import org.schabi.newpipe.util.DeviceUtils;
 import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.InfoCache;
 import org.schabi.newpipe.util.ListHelper;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PermissionHelper;
-import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.PlayButtonHelper;
 import org.schabi.newpipe.util.StreamTypeUtil;
 import org.schabi.newpipe.util.ThemeHelper;
 import org.schabi.newpipe.util.external_communication.KoreUtils;
 import org.schabi.newpipe.util.external_communication.ShareUtils;
-import org.schabi.newpipe.util.PlayButtonHelper;
+import org.schabi.newpipe.util.image.PicassoHelper;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -1445,7 +1445,7 @@ public final class VideoDetailFragment
         super.showLoading();
 
         //if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required
-        if (!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)) {
+        if (!ExtractorHelper.isCached(serviceId, url, InfoCache.Type.STREAM)) {
             binding.detailContentRootHiding.setVisibility(View.INVISIBLE);
         }
 
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index c2748f725..066d5f570 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -27,6 +27,7 @@ import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.text.HtmlCompat;
 import androidx.preference.PreferenceManager;
@@ -113,14 +114,14 @@ public final class ExtractorHelper {
     public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url,
                                                    final boolean forceLoad) {
         checkServiceId(serviceId);
-        return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM,
+        return checkCache(forceLoad, serviceId, url, InfoCache.Type.STREAM,
                 Single.fromCallable(() -> StreamInfo.getInfo(NewPipe.getService(serviceId), url)));
     }
 
     public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url,
                                                      final boolean forceLoad) {
         checkServiceId(serviceId);
-        return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL,
+        return checkCache(forceLoad, serviceId, url, InfoCache.Type.CHANNEL,
                 Single.fromCallable(() ->
                         ChannelInfo.getInfo(NewPipe.getService(serviceId), url)));
     }
@@ -130,7 +131,7 @@ public final class ExtractorHelper {
                                                        final boolean forceLoad) {
         checkServiceId(serviceId);
         return checkCache(forceLoad, serviceId,
-                listLinkHandler.getUrl(), InfoItem.InfoType.CHANNEL,
+                listLinkHandler.getUrl(), InfoCache.Type.CHANNEL_TAB,
                 Single.fromCallable(() ->
                         ChannelTabInfo.getInfo(NewPipe.getService(serviceId), listLinkHandler)));
     }
@@ -145,10 +146,11 @@ public final class ExtractorHelper {
                         listLinkHandler, nextPage));
     }
 
-    public static Single<CommentsInfo> getCommentsInfo(final int serviceId, final String url,
+    public static Single<CommentsInfo> getCommentsInfo(final int serviceId,
+                                                       final String url,
                                                        final boolean forceLoad) {
         checkServiceId(serviceId);
-        return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT,
+        return checkCache(forceLoad, serviceId, url, InfoCache.Type.COMMENTS,
                 Single.fromCallable(() ->
                         CommentsInfo.getInfo(NewPipe.getService(serviceId), url)));
     }
@@ -175,7 +177,7 @@ public final class ExtractorHelper {
                                                        final String url,
                                                        final boolean forceLoad) {
         checkServiceId(serviceId);
-        return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST,
+        return checkCache(forceLoad, serviceId, url, InfoCache.Type.PLAYLIST,
                 Single.fromCallable(() ->
                         PlaylistInfo.getInfo(NewPipe.getService(serviceId), url)));
     }
@@ -188,9 +190,10 @@ public final class ExtractorHelper {
                 PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextPage));
     }
 
-    public static Single<KioskInfo> getKioskInfo(final int serviceId, final String url,
+    public static Single<KioskInfo> getKioskInfo(final int serviceId,
+                                                 final String url,
                                                  final boolean forceLoad) {
-        return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST,
+        return checkCache(forceLoad, serviceId, url, InfoCache.Type.KIOSK,
                 Single.fromCallable(() -> KioskInfo.getInfo(NewPipe.getService(serviceId), url)));
     }
 
@@ -202,7 +205,7 @@ public final class ExtractorHelper {
     }
 
     /*//////////////////////////////////////////////////////////////////////////
-    // Utils
+    // Cache
     //////////////////////////////////////////////////////////////////////////*/
 
     /**
@@ -214,24 +217,25 @@ public final class ExtractorHelper {
      * @param forceLoad       whether to force loading from the network instead of from the cache
      * @param serviceId       the service to load from
      * @param url             the URL to load
-     * @param infoType        the {@link InfoItem.InfoType} of the item
+     * @param cacheType       the {@link InfoCache.Type} of the item
      * @param loadFromNetwork the {@link Single} to load the item from the network
      * @return a {@link Single} that loads the item
      */
     private static <I extends Info> Single<I> checkCache(final boolean forceLoad,
-                                                         final int serviceId, final String url,
-                                                         final InfoItem.InfoType infoType,
-                                                         final Single<I> loadFromNetwork) {
+                                                         final int serviceId,
+                                                         @NonNull final String url,
+                                                         @NonNull final InfoCache.Type cacheType,
+                                                         @NonNull final Single<I> loadFromNetwork) {
         checkServiceId(serviceId);
         final Single<I> actualLoadFromNetwork = loadFromNetwork
-                .doOnSuccess(info -> CACHE.putInfo(serviceId, url, info, infoType));
+                .doOnSuccess(info -> CACHE.putInfo(serviceId, url, info, cacheType));
 
         final Single<I> load;
         if (forceLoad) {
-            CACHE.removeInfo(serviceId, url, infoType);
+            CACHE.removeInfo(serviceId, url, cacheType);
             load = actualLoadFromNetwork;
         } else {
-            load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType),
+            load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, cacheType),
                             actualLoadFromNetwork.toMaybe())
                     .firstElement() // Take the first valid
                     .toSingle();
@@ -246,15 +250,17 @@ public final class ExtractorHelper {
      * @param <I>       the item type's class that extends {@link Info}
      * @param serviceId the service to load from
      * @param url       the URL to load
-     * @param infoType  the {@link InfoItem.InfoType} of the item
+     * @param cacheType the {@link InfoCache.Type} of the item
      * @return a {@link Single} that loads the item
      */
-    private static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url,
-                                                           final InfoItem.InfoType infoType) {
+    private static <I extends Info> Maybe<I> loadFromCache(
+            final int serviceId,
+            @NonNull final String url,
+            @NonNull final InfoCache.Type cacheType) {
         checkServiceId(serviceId);
         return Maybe.defer(() -> {
             //noinspection unchecked
-            final I info = (I) CACHE.getFromKey(serviceId, url, infoType);
+            final I info = (I) CACHE.getFromKey(serviceId, url, cacheType);
             if (MainActivity.DEBUG) {
                 Log.d(TAG, "loadFromCache() called, info > " + info);
             }
@@ -268,11 +274,17 @@ public final class ExtractorHelper {
         });
     }
 
-    public static boolean isCached(final int serviceId, final String url,
-                                   final InfoItem.InfoType infoType) {
-        return null != loadFromCache(serviceId, url, infoType).blockingGet();
+    public static boolean isCached(final int serviceId,
+                                   @NonNull final String url,
+                                   @NonNull final InfoCache.Type cacheType) {
+        return null != loadFromCache(serviceId, url, cacheType).blockingGet();
     }
 
+
+    /*//////////////////////////////////////////////////////////////////////////
+    // Utils
+    //////////////////////////////////////////////////////////////////////////*/
+
     /**
      * Formats the text contained in the meta info list as HTML and puts it into the text view,
      * while also making the separator visible. If the list is null or empty, or the user chose not
diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
index a07f05828..b9c91f8a5 100644
--- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
+++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java
@@ -27,7 +27,6 @@ import androidx.collection.LruCache;
 
 import org.schabi.newpipe.MainActivity;
 import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.InfoItem;
 
 import java.util.Map;
 
@@ -48,14 +47,27 @@ public final class InfoCache {
         // no instance
     }
 
+    /**
+     * Identifies the type of {@link Info} to put into the cache.
+     */
+    public enum Type {
+        STREAM,
+        CHANNEL,
+        CHANNEL_TAB,
+        COMMENTS,
+        PLAYLIST,
+        KIOSK,
+    }
+
     public static InfoCache getInstance() {
         return INSTANCE;
     }
 
     @NonNull
-    private static String keyOf(final int serviceId, @NonNull final String url,
-                                @NonNull final InfoItem.InfoType infoType) {
-        return serviceId + url + infoType.toString();
+    private static String keyOf(final int serviceId,
+                                @NonNull final String url,
+                                @NonNull final Type cacheType) {
+        return serviceId + ":" + cacheType.ordinal() + ":" + url;
     }
 
     private static void removeStaleCache() {
@@ -83,19 +95,22 @@ public final class InfoCache {
     }
 
     @Nullable
-    public Info getFromKey(final int serviceId, @NonNull final String url,
-                           @NonNull final InfoItem.InfoType infoType) {
+    public Info getFromKey(final int serviceId,
+                           @NonNull final String url,
+                           @NonNull final Type cacheType) {
         if (DEBUG) {
             Log.d(TAG, "getFromKey() called with: "
                     + "serviceId = [" + serviceId + "], url = [" + url + "]");
         }
         synchronized (LRU_CACHE) {
-            return getInfo(keyOf(serviceId, url, infoType));
+            return getInfo(keyOf(serviceId, url, cacheType));
         }
     }
 
-    public void putInfo(final int serviceId, @NonNull final String url, @NonNull final Info info,
-                        @NonNull final InfoItem.InfoType infoType) {
+    public void putInfo(final int serviceId,
+                        @NonNull final String url,
+                        @NonNull final Info info,
+                        @NonNull final Type cacheType) {
         if (DEBUG) {
             Log.d(TAG, "putInfo() called with: info = [" + info + "]");
         }
@@ -103,18 +118,19 @@ public final class InfoCache {
         final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId());
         synchronized (LRU_CACHE) {
             final CacheData data = new CacheData(info, expirationMillis);
-            LRU_CACHE.put(keyOf(serviceId, url, infoType), data);
+            LRU_CACHE.put(keyOf(serviceId, url, cacheType), data);
         }
     }
 
-    public void removeInfo(final int serviceId, @NonNull final String url,
-                           @NonNull final InfoItem.InfoType infoType) {
+    public void removeInfo(final int serviceId,
+                           @NonNull final String url,
+                           @NonNull final Type cacheType) {
         if (DEBUG) {
             Log.d(TAG, "removeInfo() called with: "
                     + "serviceId = [" + serviceId + "], url = [" + url + "]");
         }
         synchronized (LRU_CACHE) {
-            LRU_CACHE.remove(keyOf(serviceId, url, infoType));
+            LRU_CACHE.remove(keyOf(serviceId, url, cacheType));
         }
     }
 

From 55373c95d94262d98cb877d235dea1a2fca8dce9 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Dec 2023 23:49:09 +0100
Subject: [PATCH 086/141] Update NewPipeExtractor to include MediaCCC channel
 fix

---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 6b51f6029..36bb35dc9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -198,7 +198,7 @@ dependencies {
     // name and the commit hash with the commit hash of the (pushed) commit you want to test
     // This works thanks to JitPack: https://jitpack.io/
     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
-    implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.23.1'
+    implementation 'com.github.Stypox:NewPipeExtractor:aaf3231fc75d7b4177549fec4aa7e672bfe84015'
     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
 
 /** Checkstyle **/

From 66e8e2a6968081abd850d81bc32fbacb7919ecaf Mon Sep 17 00:00:00 2001
From: Roshan Jossy <roshanjossey@gmail.com>
Date: Mon, 1 Jan 2024 15:07:37 +0100
Subject: [PATCH 087/141] use GitHub markdown to emphasise warning in Readme

---
 README.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 9fabb5ac7..d159c77fc 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,10 @@
 
 *Read this document in other languages: [Deutsch](doc/README.de.md), [English](README.md), [Español](doc/README.es.md), [Français](doc/README.fr.md), [हिन्दी](doc/README.hi.md), [Italiano](doc/README.it.md), [한국어](doc/README.ko.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [ਪੰਜਾਬੀ ](doc/README.pa.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Soomaali](doc/README.so.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md), [অসমীয়া](doc/README.asm.md), [Српски](doc/README.sr.md)*
 
-<b>WARNING: THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b>
-
-<b>PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.</b>
+> [!warning]
+> <b>THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b>
+> 
+> <b>PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.</b>
 
 ## Screenshots
 

From 575e8090043dde229bf9c8b2057c869042a0e6a7 Mon Sep 17 00:00:00 2001
From: Profpatsch <mail@profpatsch.de>
Date: Sat, 6 Jan 2024 17:24:53 +0100
Subject: [PATCH 088/141] Don't write defaultFormat setting, use default value
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Nowhere else does this (write a setting if it’s not set).

It took me a while to see that this code does not do what it intends,
because `defaultFormat` is already the default value in the first
`context.getString`, so calling `getMediaFormatFromKey` again is the
exact same call (“do you know the definition of insanity…”) and will
return `null` again …

So let’s drop the setting write and just rely on the default values.
---
 .../java/org/schabi/newpipe/util/ListHelper.java  | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
index 71071d997..7cb8f1f37 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -643,6 +643,7 @@ public final class ListHelper {
                 context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
     }
 
+    @Nullable
     private static MediaFormat getDefaultFormat(@NonNull final Context context,
                                                 @StringRes final int defaultFormatKey,
                                                 @StringRes final int defaultFormatValueKey) {
@@ -651,18 +652,14 @@ public final class ListHelper {
 
         final String defaultFormat = context.getString(defaultFormatValueKey);
         final String defaultFormatString = preferences.getString(
-                context.getString(defaultFormatKey), defaultFormat);
+                context.getString(defaultFormatKey),
+                defaultFormat
+        );
 
-        MediaFormat defaultMediaFormat = getMediaFormatFromKey(context, defaultFormatString);
-        if (defaultMediaFormat == null) {
-            preferences.edit().putString(context.getString(defaultFormatKey), defaultFormat)
-                    .apply();
-            defaultMediaFormat = getMediaFormatFromKey(context, defaultFormat);
-        }
-
-        return defaultMediaFormat;
+        return getMediaFormatFromKey(context, defaultFormatString);
     }
 
+    @Nullable
     private static MediaFormat getMediaFormatFromKey(@NonNull final Context context,
                                                      @NonNull final String formatKey) {
         MediaFormat format = null;

From 2051334bba247422dc8203fa10f89f998ee8642a Mon Sep 17 00:00:00 2001
From: Zongle Wang <wangzongler@gmail.com>
Date: Mon, 8 Jan 2024 11:55:57 +0800
Subject: [PATCH 089/141] Bump GH actions

Old ones are deprecated.
---
 .github/workflows/ci.yml              | 16 ++++++++--------
 .github/workflows/image-minimizer.yml |  6 +++---
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 03b04b7c4..4800f8578 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,7 +36,7 @@ jobs:
       contents: read
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - uses: gradle/wrapper-validation-action@v1
 
       - name: create and checkout branch
@@ -47,7 +47,7 @@ jobs:
         run: git checkout -B "$BRANCH"
 
       - name: set up JDK 17
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           java-version: 17
           distribution: "temurin"
@@ -57,7 +57,7 @@ jobs:
         run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
 
       - name: Upload APK
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: app
           path: app/build/outputs/apk/debug/*.apk
@@ -80,10 +80,10 @@ jobs:
       contents: read
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: set up JDK 17
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           java-version: 17
           distribution: "temurin"
@@ -98,7 +98,7 @@ jobs:
           script: ./gradlew connectedCheck --stacktrace
 
       - name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         if: failure()
         with:
           name: android-test-report-api${{ matrix.api-level }}
@@ -111,12 +111,12 @@ jobs:
       contents: read
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
 
       - name: Set up JDK 17
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           java-version: 17
           distribution: "temurin"
diff --git a/.github/workflows/image-minimizer.yml b/.github/workflows/image-minimizer.yml
index 56c6eb37f..d9241c33b 100644
--- a/.github/workflows/image-minimizer.yml
+++ b/.github/workflows/image-minimizer.yml
@@ -17,9 +17,9 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
-      - uses: actions/setup-node@v3
+      - uses: actions/setup-node@v4
         with:
           node-version: 16
 
@@ -27,7 +27,7 @@ jobs:
         run: npm i probe-image-size@7.2.3 --ignore-scripts
 
       - name: Minimize simple images
-        uses: actions/github-script@v6
+        uses: actions/github-script@v7
         timeout-minutes: 3
         with:
           script: |

From 32d2606a6544961ecd8766ea445c729c301ed14f Mon Sep 17 00:00:00 2001
From: Profpatsch <mail@profpatsch.de>
Date: Sat, 20 Jan 2024 22:26:24 +0100
Subject: [PATCH 090/141] BaseDescriptionFragment: Assert member is initialized

`streamInfo` and `channelInfo` have to be initialized, since the only
way to construct the class it to pass them. So we can remove the null
check boilerplate and make some of the accessors `NonNull`.
---
 .../detail/BaseDescriptionFragment.java       | 26 +++++++--------
 .../fragments/detail/DescriptionFragment.java | 26 +++------------
 .../list/channel/ChannelAboutFragment.java    | 33 +++++++------------
 3 files changed, 27 insertions(+), 58 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseDescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseDescriptionFragment.java
index e3bdc6592..4789b02e6 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseDescriptionFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseDescriptionFragment.java
@@ -64,7 +64,7 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
 
     /**
      * Get the description to display.
-     * @return description object
+     * @return description object, if available
      */
     @Nullable
     protected abstract Description getDescription();
@@ -73,7 +73,7 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
      * Get the streaming service. Used for generating description links.
      * @return streaming service
      */
-    @Nullable
+    @NonNull
     protected abstract StreamingService getService();
 
     /**
@@ -93,7 +93,7 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
      * Get the list of tags to display below the description.
      * @return tag list
      */
-    @Nullable
+    @NonNull
     public abstract List<String> getTags();
 
     /**
@@ -158,7 +158,7 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
                                    final LinearLayout layout,
                                    final boolean linkifyContent,
                                    @StringRes final int type,
-                                   @Nullable final String content) {
+                                   @NonNull final String content) {
         if (isBlank(content)) {
             return;
         }
@@ -221,16 +221,12 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
                 urls.append(imageSizeToText(image.getWidth()));
             } else {
                 switch (image.getEstimatedResolutionLevel()) {
-                    case LOW:
-                        urls.append(getString(R.string.image_quality_low));
-                        break;
-                    default: // unreachable, Image.ResolutionLevel.UNKNOWN is already filtered out
-                    case MEDIUM:
-                        urls.append(getString(R.string.image_quality_medium));
-                        break;
-                    case HIGH:
-                        urls.append(getString(R.string.image_quality_high));
-                        break;
+                    case LOW -> urls.append(getString(R.string.image_quality_low));
+                    case MEDIUM -> urls.append(getString(R.string.image_quality_medium));
+                    case HIGH -> urls.append(getString(R.string.image_quality_high));
+                    default -> {
+                        // unreachable, Image.ResolutionLevel.UNKNOWN is already filtered out
+                    }
                 }
             }
 
@@ -255,7 +251,7 @@ public abstract class BaseDescriptionFragment extends BaseFragment {
     private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
         final List<String> tags = getTags();
 
-        if (tags != null && !tags.isEmpty()) {
+        if (!tags.isEmpty()) {
             final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false);
 
             tags.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
index ada12fc8e..ba233559c 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
@@ -7,6 +7,7 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
@@ -23,10 +24,8 @@ import icepick.State;
 public class DescriptionFragment extends BaseDescriptionFragment {
 
     @State
-    StreamInfo streamInfo = null;
+    StreamInfo streamInfo;
 
-    public DescriptionFragment() {
-    }
 
     public DescriptionFragment(final StreamInfo streamInfo) {
         this.streamInfo = streamInfo;
@@ -35,44 +34,29 @@ public class DescriptionFragment extends BaseDescriptionFragment {
     @Nullable
     @Override
     protected Description getDescription() {
-        if (streamInfo == null) {
-            return null;
-        }
         return streamInfo.getDescription();
     }
 
-    @Nullable
+    @NonNull
     @Override
     protected StreamingService getService() {
-        if (streamInfo == null) {
-            return null;
-        }
         return streamInfo.getService();
     }
 
     @Override
     protected int getServiceId() {
-        if (streamInfo == null) {
-            return -1;
-        }
         return streamInfo.getServiceId();
     }
 
-    @Nullable
+    @NonNull
     @Override
     protected String getStreamUrl() {
-        if (streamInfo == null) {
-            return null;
-        }
         return streamInfo.getUrl();
     }
 
-    @Nullable
+    @NonNull
     @Override
     public List<String> getTags() {
-        if (streamInfo == null) {
-            return null;
-        }
         return streamInfo.getTags();
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
index 56418800d..30c3aac5a 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
@@ -2,12 +2,12 @@ package org.schabi.newpipe.fragments.list.channel;
 
 import static org.schabi.newpipe.extractor.stream.StreamExtractor.UNKNOWN_SUBSCRIBER_COUNT;
 
-import android.content.Context;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import org.schabi.newpipe.R;
@@ -26,15 +26,12 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
     @State
     protected ChannelInfo channelInfo;
 
-    public static ChannelAboutFragment getInstance(final ChannelInfo channelInfo) {
+    public static ChannelAboutFragment getInstance(final @NonNull ChannelInfo channelInfo) {
         final ChannelAboutFragment fragment = new ChannelAboutFragment();
         fragment.channelInfo = channelInfo;
         return fragment;
     }
 
-    public ChannelAboutFragment() {
-        super();
-    }
 
     @Override
     protected void initViews(final View rootView, final Bundle savedInstanceState) {
@@ -45,26 +42,20 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
     @Nullable
     @Override
     protected Description getDescription() {
-        if (channelInfo == null) {
-            return null;
-        }
-        return new Description(channelInfo.getDescription(), Description.PLAIN_TEXT);
+        return new Description(
+                channelInfo.getDescription(),
+                Description.PLAIN_TEXT
+        );
     }
 
-    @Nullable
+    @NonNull
     @Override
     protected StreamingService getService() {
-        if (channelInfo == null) {
-            return null;
-        }
         return channelInfo.getService();
     }
 
     @Override
     protected int getServiceId() {
-        if (channelInfo == null) {
-            return -1;
-        }
         return channelInfo.getServiceId();
     }
 
@@ -74,12 +65,9 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
         return null;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public List<String> getTags() {
-        if (channelInfo == null) {
-            return null;
-        }
         return channelInfo.getTags();
     }
 
@@ -93,10 +81,11 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
             return;
         }
 
-        final Context context = getContext();
         if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) {
             addMetadataItem(inflater, layout, false, R.string.metadata_subscribers,
-                    Localization.localizeNumber(context, channelInfo.getSubscriberCount()));
+                    Localization.localizeNumber(
+                            requireContext(),
+                            channelInfo.getSubscriberCount()));
         }
 
         addImagesMetadataItem(inflater, layout, R.string.metadata_avatars,

From 6dd62335e923cd5fd57d2d48d6790805897816a2 Mon Sep 17 00:00:00 2001
From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Date: Sat, 27 Jan 2024 16:36:13 +0000
Subject: [PATCH 091/141] Update Matrix room URL to new link

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9fabb5ac7..cec679a58 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
 <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
 <a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
 <a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
-<a href="https://matrix.to/#/#newpipe:libera.chat" alt="Matrix channel: #newpipe"><img src="https://img.shields.io/badge/Matrix%20chat-%23newpipe-blue"></a>
+<a href="https://matrix.to/#/#newpipe:matrix.newpipe-ev.de" alt="Matrix channel: #newpipe"><img src="https://img.shields.io/badge/Matrix%20chat-%23newpipe-blue"></a>
 </p>
 <hr>
 <p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#supported-services">Supported Services</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#installation-and-updates">Installation and updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>

From 27730a20d6c76725cd14dc7f00ecf146995fdcc9 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <isirasen96@gmail.com>
Date: Mon, 5 Feb 2024 10:52:08 +0530
Subject: [PATCH 092/141] Update Jsoup to 1.17.2

---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 6b51f6029..60414e0e5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -238,7 +238,7 @@ dependencies {
     kapt "frankiesardo:icepick-processor:${icepickVersion}"
 
     // HTML parser
-    implementation "org.jsoup:jsoup:1.16.2"
+    implementation "org.jsoup:jsoup:1.17.2"
 
     // HTTP client
     implementation "com.squareup.okhttp3:okhttp:4.12.0"

From 9d6ac67c46a6cc55e03dec0599d8070f7430098d Mon Sep 17 00:00:00 2001
From: Ikko Eltociear Ashimine <eltociear@gmail.com>
Date: Mon, 18 Mar 2024 14:43:16 +0900
Subject: [PATCH 093/141] Update TextLinkifier.java

minor fix
---
 .../main/java/org/schabi/newpipe/util/text/TextLinkifier.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java
index e59a3dc05..1419ac85a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/text/TextLinkifier.java
@@ -92,7 +92,7 @@ public final class TextLinkifier {
      * {@link HtmlCompat#fromHtml(String, int)}.
      * </p>
      *
-     * @param textView           the {@link TextView} to set the the HTML string block linked
+     * @param textView           the {@link TextView} to set the HTML string block linked
      * @param htmlBlock          the HTML string block to be linked
      * @param htmlCompatFlag     the int flag to be set when {@link HtmlCompat#fromHtml(String,
      *                           int)} will be called

From e6a4a3fa4f1f1c8cee90a207efd5f8896dcbc25c Mon Sep 17 00:00:00 2001
From: Hosted Weblate <hosted@weblate.org>
Date: Mon, 18 Mar 2024 09:59:34 +0100
Subject: [PATCH 094/141] Translated using Weblate (Danish)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently translated at 96.4% (703 of 729 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 96.4% (703 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 88.3% (644 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 88.3% (644 of 729 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Georgian)

Currently translated at 92.2% (71 of 77 strings)

Translated using Weblate (Uzbek (latin))

Currently translated at 62.6% (457 of 729 strings)

Translated using Weblate (Santali)

Currently translated at 12.6% (92 of 729 strings)

Translated using Weblate (French)

Currently translated at 89.6% (69 of 77 strings)

Translated using Weblate (Japanese)

Currently translated at 11.6% (9 of 77 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.1% (4 of 77 strings)

Translated using Weblate (Bengali)

Currently translated at 20.7% (16 of 77 strings)

Translated using Weblate (German)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Bengali (India))

Currently translated at 40.7% (297 of 729 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 85.5% (624 of 729 strings)

Translated using Weblate (Tamil)

Currently translated at 46.6% (340 of 729 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 55.1% (402 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 98.7% (76 of 77 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.4% (725 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 99.4% (725 of 729 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 2.5% (2 of 77 strings)

Translated using Weblate (Malay)

Currently translated at 48.6% (355 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 99.3% (724 of 729 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Finnish)

Currently translated at 98.3% (717 of 729 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 22.0% (17 of 77 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 95.4% (696 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Vietnamese)

Currently translated at 42.8% (33 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Esperanto)

Currently translated at 70.7% (516 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 94.6% (690 of 729 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (French)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Santali)

Currently translated at 10.0% (73 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 42.8% (33 of 77 strings)

Translated using Weblate (German)

Currently translated at 81.8% (63 of 77 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Kannada)

Currently translated at 5.4% (40 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 88.7% (647 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 20.7% (16 of 77 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (French)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Catalan)

Currently translated at 86.5% (631 of 729 strings)

Translated using Weblate (Telugu)

Currently translated at 58.9% (430 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Tigrinya)

Currently translated at 8.9% (65 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (76 of 77 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (727 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 85.5% (624 of 729 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Serbian)

Currently translated at 18.1% (14 of 77 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Greek)

Currently translated at 24.6% (19 of 77 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (German)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Kannada)

Currently translated at 5.5% (40 of 726 strings)

Translated using Weblate (Sinhala)

Currently translated at 2.6% (2 of 76 strings)

Translated using Weblate (Sinhala)

Currently translated at 4.1% (30 of 726 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.2% (4 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Turkish)

Currently translated at 99.8% (725 of 726 strings)

Translated using Weblate (Slovak)

Currently translated at 98.4% (715 of 726 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (French)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Italian)

Currently translated at 99.5% (723 of 726 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (German)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 21.0% (16 of 76 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Romanian)

Currently translated at 99.8% (724 of 725 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (725 of 725 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Alexthegib <traducoes@skiff.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Angelk90 <angelo.k90@hotmail.it>
Co-authored-by: Chethan <76928501+ch3thanhs@users.noreply.github.com>
Co-authored-by: Danr <mdp43140@gmail.com>
Co-authored-by: David Svane <davidcygnus@users.noreply.hosted.weblate.org>
Co-authored-by: Deleted User <noreply+77891@weblate.org>
Co-authored-by: DuninduH <mateyh37@gmail.com>
Co-authored-by: Eric <zxmegaxqug@hldrive.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Heidhou chazanouvha <Heidhou@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihfandi <ihfandicahyo@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jan Layola <gilajan@protonmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Juan Martinez <jjml.nipon@gmail.com>
Co-authored-by: KarmaKat <lloydwestbury@gmail.com>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: LiJu09 <lisojuraj@gmail.com>
Co-authored-by: Martin Constantino–Bodin <martin.bodin@ens-lyon.org>
Co-authored-by: Mehmet <mehmetyalcin.0103@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nils Van Zuijlen <nils.van-zuijlen@mailo.com>
Co-authored-by: Nista <42772160+Nista11@users.noreply.github.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: P.O <rasmusson.mikael@protonmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Pi-Cla <pirateclip@protonmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Ray <ray@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Subbarayudu <raidu.g6@gmail.com>
Co-authored-by: Subham Jena <subhamjena8465@gmail.com>
Co-authored-by: T1z3n <info@njbraun.de>
Co-authored-by: Terry Louwers <t.louwers@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Xəyyam Qocayev <xxmn77@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: cat <158170307+cultcats@users.noreply.github.com>
Co-authored-by: ds-z <drazen.sostaric01@gmail.com>
Co-authored-by: dyare darbani <darbanidyare@gmail.com>
Co-authored-by: fsbat0 <fsbat0@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: hshbuk <hsh.bukchin@gmail.com>
Co-authored-by: jspast <joao.pastorello@protonmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Co-authored-by: Åzze <laitinen.jere222@gmail.com>
Co-authored-by: Çağla Pickaxe <caglapickaxe@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/el/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/si/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
---
 app/src/main/res/values-ar/strings.xml        |  13 +-
 app/src/main/res/values-az/strings.xml        |   3 +-
 app/src/main/res/values-b+uz+Latn/strings.xml |   2 +-
 app/src/main/res/values-be/strings.xml        | 125 ++--
 app/src/main/res/values-bn-rBD/strings.xml    |   6 +-
 app/src/main/res/values-bn-rIN/strings.xml    |   2 +-
 app/src/main/res/values-ca/strings.xml        |  23 +-
 app/src/main/res/values-ckb/strings.xml       |  60 +-
 app/src/main/res/values-cs/strings.xml        |  10 +-
 app/src/main/res/values-da/strings.xml        | 228 +++---
 app/src/main/res/values-de/strings.xml        |  13 +-
 app/src/main/res/values-el/strings.xml        |  13 +-
 app/src/main/res/values-eo/strings.xml        |   1 +
 app/src/main/res/values-es/strings.xml        |  12 +-
 app/src/main/res/values-et/strings.xml        |   9 +-
 app/src/main/res/values-fi/strings.xml        |   9 +-
 app/src/main/res/values-fr/strings.xml        |  10 +-
 app/src/main/res/values-he/strings.xml        |  10 +-
 app/src/main/res/values-hi/strings.xml        |  13 +-
 app/src/main/res/values-hr/strings.xml        | 213 ++++--
 app/src/main/res/values-hu/strings.xml        |  53 +-
 app/src/main/res/values-in/strings.xml        |  42 +-
 app/src/main/res/values-it/strings.xml        |  12 +-
 app/src/main/res/values-ja/strings.xml        |   8 +-
 app/src/main/res/values-kn/strings.xml        |  43 +-
 app/src/main/res/values-ms/strings.xml        |   4 +
 app/src/main/res/values-nb-rNO/strings.xml    |  30 +-
 app/src/main/res/values-nl/strings.xml        |  15 +-
 app/src/main/res/values-or/strings.xml        |  11 +-
 app/src/main/res/values-pa/strings.xml        |   9 +-
 app/src/main/res/values-pl/strings.xml        |   9 +
 app/src/main/res/values-pt-rBR/strings.xml    |  11 +-
 app/src/main/res/values-pt-rPT/strings.xml    |  10 +-
 app/src/main/res/values-pt/strings.xml        | 234 +++---
 app/src/main/res/values-ro/strings.xml        |  19 +-
 app/src/main/res/values-ru/strings.xml        |  15 +-
 app/src/main/res/values-ryu/strings.xml       |  23 +-
 app/src/main/res/values-sat/strings.xml       | 687 +++++++++++++++++-
 app/src/main/res/values-si/strings.xml        |   7 +
 app/src/main/res/values-sk/strings.xml        | 149 ++--
 app/src/main/res/values-sr/strings.xml        |  30 +-
 app/src/main/res/values-sv/strings.xml        |   9 +-
 app/src/main/res/values-ta/strings.xml        |   4 +-
 app/src/main/res/values-te/strings.xml        |  13 +-
 app/src/main/res/values-ti/strings.xml        |   7 +-
 app/src/main/res/values-tr/strings.xml        |  51 +-
 app/src/main/res/values-uk/strings.xml        |  13 +-
 app/src/main/res/values-vi/strings.xml        |  10 +-
 app/src/main/res/values-zh-rCN/strings.xml    |   8 +-
 app/src/main/res/values-zh-rHK/strings.xml    |  14 +-
 app/src/main/res/values-zh-rTW/strings.xml    |   8 +-
 .../metadata/android/ar/changelogs/996.txt    |   2 +
 .../metadata/android/bg/changelogs/64.txt     |   6 +
 .../metadata/android/bn/changelogs/64.txt     |   2 +-
 .../metadata/android/cs/changelogs/996.txt    |   2 +
 .../metadata/android/de/changelogs/65.txt     |   4 +-
 .../metadata/android/de/changelogs/66.txt     |  18 +
 .../metadata/android/de/changelogs/68.txt     |  19 +
 .../metadata/android/de/changelogs/69.txt     |  14 +
 .../metadata/android/de/changelogs/70.txt     |  10 +
 .../metadata/android/de/changelogs/71.txt     |   8 +
 .../metadata/android/de/changelogs/740.txt    |  12 +
 .../metadata/android/de/changelogs/750.txt    |  15 +
 .../metadata/android/de/changelogs/760.txt    |  24 +
 .../metadata/android/de/changelogs/780.txt    |  11 +
 .../metadata/android/de/changelogs/790.txt    |  13 +
 .../metadata/android/de/changelogs/800.txt    |  11 +
 .../metadata/android/de/changelogs/810.txt    |  11 +
 .../metadata/android/de/changelogs/840.txt    |  10 +
 .../metadata/android/de/changelogs/930.txt    |  12 +
 .../metadata/android/de/changelogs/940.txt    |   1 -
 .../metadata/android/de/changelogs/951.txt    |   1 -
 .../metadata/android/de/changelogs/990.txt    |   2 -
 .../metadata/android/de/changelogs/995.txt    |   4 +-
 .../metadata/android/de/changelogs/996.txt    |   2 +
 .../metadata/android/de/full_description.txt  |   2 +-
 .../metadata/android/de/short_description.txt |   2 +-
 .../metadata/android/el/changelogs/65.txt     |  26 +
 .../metadata/android/el/changelogs/963.txt    |   1 +
 .../metadata/android/el/changelogs/996.txt    |   2 +
 .../metadata/android/es/changelogs/996.txt    |   2 +
 .../metadata/android/fr/changelogs/750.txt    |   6 +-
 .../metadata/android/hi/changelogs/996.txt    |   2 +
 .../metadata/android/hr/full_description.txt  |   2 +-
 .../metadata/android/id/changelogs/66.txt     |  12 +-
 .../metadata/android/id/changelogs/972.txt    |   2 +-
 .../metadata/android/id/changelogs/975.txt    |  28 +-
 .../metadata/android/id/changelogs/976.txt    |   6 +-
 .../metadata/android/id/changelogs/995.txt    |  12 +-
 .../metadata/android/id/changelogs/996.txt    |   2 +
 .../metadata/android/it/changelogs/996.txt    |   2 +
 .../metadata/android/ja/changelogs/954.txt    |   2 +-
 .../metadata/android/ka/changelogs/64.txt     |   2 +-
 .../metadata/android/ka/changelogs/69.txt     |   2 +-
 .../metadata/android/ka/changelogs/740.txt    |   6 +-
 .../metadata/android/ka/changelogs/850.txt    |   2 +-
 .../metadata/android/ka/changelogs/967.txt    |   2 +-
 .../metadata/android/ka/changelogs/978.txt    |   2 +-
 .../metadata/android/ka/changelogs/988.txt    |   2 +-
 .../metadata/android/ka/full_description.txt  |   2 +-
 .../metadata/android/pa/changelogs/996.txt    |   2 +
 .../metadata/android/pt-PT/changelogs/995.txt |  16 +
 .../metadata/android/pt/changelogs/995.txt    |  10 +-
 .../metadata/android/pt/changelogs/996.txt    |   2 +
 .../metadata/android/ru/changelogs/995.txt    |  16 +-
 .../metadata/android/ru/changelogs/996.txt    |   2 +
 .../metadata/android/si/full_description.txt  |   1 +
 .../metadata/android/si/short_description.txt |   1 +
 .../metadata/android/sk/changelogs/995.txt    |  16 +
 .../metadata/android/sk/changelogs/996.txt    |   2 +
 .../metadata/android/sr/changelogs/996.txt    |   2 +
 .../metadata/android/sv/changelogs/63.txt     |   4 +-
 .../metadata/android/sv/changelogs/64.txt     |   6 +-
 .../metadata/android/sv/changelogs/957.txt    |  10 +
 .../metadata/android/sv/changelogs/958.txt    |  10 +
 .../metadata/android/sv/changelogs/961.txt    |  12 +
 .../metadata/android/sv/changelogs/964.txt    |   7 +
 .../metadata/android/sv/changelogs/965.txt    |   6 +
 .../metadata/android/sv/changelogs/966.txt    |  14 +
 .../metadata/android/sv/changelogs/968.txt    |   7 +
 .../metadata/android/sv/changelogs/970.txt    |  11 +
 .../metadata/android/sv/changelogs/975.txt    |  15 +
 .../metadata/android/sv/changelogs/976.txt    |   9 +
 .../metadata/android/sv/changelogs/983.txt    |   9 +
 .../metadata/android/sv/changelogs/986.txt    |  15 +
 .../metadata/android/sv/changelogs/987.txt    |  12 +
 .../metadata/android/sv/changelogs/988.txt    |   2 +
 .../metadata/android/sv/changelogs/989.txt    |   3 +
 .../metadata/android/sv/changelogs/990.txt    |  15 +
 .../metadata/android/sv/changelogs/991.txt    |  13 +
 .../metadata/android/sv/changelogs/992.txt    |  17 +
 .../metadata/android/sv/changelogs/993.txt    |  12 +
 .../metadata/android/sv/changelogs/994.txt    |  15 +
 .../metadata/android/sv/changelogs/995.txt    |  16 +
 .../metadata/android/sv/changelogs/996.txt    |   2 +
 .../metadata/android/sv/short_description.txt |   2 +-
 .../metadata/android/tr/full_description.txt  |   4 +-
 .../metadata/android/uk/changelogs/996.txt    |   2 +
 .../metadata/android/vi/changelogs/996.txt    |   2 +
 .../android/zh_Hant_HK/changelogs/995.txt     |   8 +-
 .../android/zh_Hant_HK/changelogs/996.txt     |   2 +
 141 files changed, 2410 insertions(+), 600 deletions(-)
 create mode 100644 fastlane/metadata/android/ar/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/bg/changelogs/64.txt
 create mode 100644 fastlane/metadata/android/cs/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/66.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/68.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/69.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/70.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/71.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/740.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/750.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/760.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/780.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/790.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/800.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/810.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/840.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/930.txt
 create mode 100644 fastlane/metadata/android/de/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/el/changelogs/65.txt
 create mode 100644 fastlane/metadata/android/el/changelogs/963.txt
 create mode 100644 fastlane/metadata/android/el/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/es/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/hi/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/id/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/it/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/pa/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/pt-PT/changelogs/995.txt
 create mode 100644 fastlane/metadata/android/pt/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/ru/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/si/full_description.txt
 create mode 100644 fastlane/metadata/android/si/short_description.txt
 create mode 100644 fastlane/metadata/android/sk/changelogs/995.txt
 create mode 100644 fastlane/metadata/android/sk/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/sr/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/957.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/958.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/961.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/964.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/965.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/966.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/968.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/970.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/975.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/976.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/983.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/986.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/987.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/988.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/989.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/990.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/991.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/992.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/993.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/994.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/995.txt
 create mode 100644 fastlane/metadata/android/sv/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/uk/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/vi/changelogs/996.txt
 create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/996.txt

diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 6bfe11eaa..ce62198cd 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -584,7 +584,7 @@
     <string name="notification_action_shuffle">خلط</string>
     <string name="notification_action_repeat">تكرار</string>
     <string name="notification_actions_at_most_three">يمكنك تحديد ثلاثة إجراءات كحد أقصى لإظهارها في الإشعار المضغوط!</string>
-    <string name="notification_actions_summary">قم بتحرير كل إشعار أدناه من خلال النقر عليه. حدد ما يصل إلى ثلاثة منها لتظهر في الإشعار المضغوط باستخدام مربعات الاختيار الموجودة على اليمين</string>
+    <string name="notification_actions_summary">قم بتحرير كل إجراء إعلام أدناه من خلال النقر عليه. حدد ما يصل إلى ثلاثة منها ليتم عرضها في الإشعار المضغوط باستخدام مربعات الاختيار الموجودة على اليمين.</string>
     <string name="notification_action_4_title">زر الإجراء الخامس</string>
     <string name="notification_action_3_title">زر الإجراء الرابع</string>
     <string name="notification_action_2_title">زر الإجراء الثالث</string>
@@ -858,4 +858,15 @@
     <string name="share_playlist">مشاركة قائمة التشغيل</string>
     <string name="share_playlist_with_titles_message">شارك تفاصيل قائمة التشغيل مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين URL للفيديو</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="zero">رد %s</item>
+        <item quantity="one">رد %s</item>
+        <item quantity="two">ردان%s</item>
+        <item quantity="few">ردود%s</item>
+        <item quantity="many">ردود %s</item>
+        <item quantity="other">ردود %s</item>
+    </plurals>
+    <string name="show_more">عرض المزيد</string>
+    <string name="show_less">عرض أقل</string>
+    <string name="notification_actions_summary_android13">قم بتحرير كل إجراء إعلام أدناه من خلال النقر عليه. يتم تعيين الإجراءات الثلاثة الأولى (تشغيل/إيقاف مؤقت، السابق والتالي) بواسطة النظام ولا يمكن تخصيصها.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index e3de4f4d2..da08069ac 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -694,7 +694,7 @@
     <string name="youtube_music_premium_content">Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
     <string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və linklər kliklənməyə bilər.</string>
     <string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs</string>
-    <string name="notification_actions_summary">Aşağıdakı hər bir bildiriş fəaliyyətini üzərinə toxunaraq redaktə et. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərmək üçün onların üçünü seç</string>
+    <string name="notification_actions_summary">Aşağıdakı hər bir bildiriş fəaliyyətini üzərinə toxunaraq düzəliş edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərmək üçün onların üçünü seçin.</string>
     <string name="invalid_source">Belə fayl/məzmun mənbəyi yoxdur</string>
     <string name="selected_stream_external_player_not_supported">Seçilən yayım xarici oynadıcılar tərəfindən dəstəklənmir</string>
     <string name="streams_not_yet_supported_removed">Yükləyici tərəfindən hələ dəstəklənməyən yayımlar göstərilmir</string>
@@ -769,4 +769,5 @@
     <string name="feed_fetch_channel_tabs_summary">Axın yenilənərkən əldə edilən səhifələr.Kanal sürətli rejim istifadə edərək yenilənirsə, bu seçimin heç bir təsiri yoxdur.</string>
     <string name="metadata_uploader_avatars">Yükləyici avatarları</string>
     <string name="metadata_thumbnails">Miniatürlər</string>
+    <string name="notification_actions_summary_android13">Aşağıdakı hər bildirişə vuraraq ona düzəliş edin. İlk üç əməl (oynatma/fasilə, əvvəlki və sonrakı) sistem tərəfindən təyin olunub və dəyişdirilə bilməz.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-b+uz+Latn/strings.xml b/app/src/main/res/values-b+uz+Latn/strings.xml
index 0ba69ae41..3bd940d11 100644
--- a/app/src/main/res/values-b+uz+Latn/strings.xml
+++ b/app/src/main/res/values-b+uz+Latn/strings.xml
@@ -58,7 +58,7 @@
     <string name="play_with_kodi_title">Kodi bilan ijro etish</string>
     <string name="show_higher_resolutions_summary">Faqat ba\'zi qurilmalar 2K / 4K videolarni ijro etishi mumkin</string>
     <string name="show_higher_resolutions_title">Yuqori o\'lchamlarni ko\'rsatish</string>
-    <string name="default_popup_resolution_title">"Standart  pop-up o\'lchamlari"</string>
+    <string name="default_popup_resolution_title">Standart pop-up o\'lchamlari</string>
     <string name="default_resolution_title">Standart o\'lchamlari</string>
     <string name="download_path_audio_dialog_title">Audio fayllar uchun yuklab olish papkasini tanlash</string>
     <string name="download_path_summary">Yuklab olingan videofayllar shu yerda saqlanadi</string>
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 38905ed7f..ed24aac81 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -6,8 +6,8 @@
     <string name="no_player_found_toast">Патокавы плэер не знойдзены (вы можаце ўсталяваць VLC каб прайграць).</string>
     <string name="install">Усталяваць</string>
     <string name="cancel">Скасаваць</string>
-    <string name="open_in_browser">Адкрыць у браўзеры</string>
-    <string name="open_in_popup_mode">Адкрыць у асобным акне</string>
+    <string name="open_in_browser">Адкрыць ў браўзеры</string>
+    <string name="open_in_popup_mode">Адкрыць ў асобным акне</string>
     <string name="share">Падзяліцца</string>
     <string name="download">Спампаваць</string>
     <string name="controls_download_desc">Загрузка файла прамой трансляцыі</string>
@@ -37,12 +37,12 @@
     <string name="download_path_audio_summary">Загружаныя аўдыёфайлы захоўваюцца тут</string>
     <string name="download_path_audio_dialog_title">Абярыце тэчку загрузкі для аўдыёфайлаў</string>
     <string name="default_resolution_title">Разрознянне па змаўчанні</string>
-    <string name="default_popup_resolution_title">Разрозненне усплываючага акна</string>
+    <string name="default_popup_resolution_title">Разрозненне ўсплываючага акна</string>
     <string name="show_higher_resolutions_title">Высокія разрозненні</string>
     <string name="show_higher_resolutions_summary">Толькі некаторыя прылады могуць прайграваць відэа ў 2K/4K</string>
-    <string name="play_with_kodi_title">Прайграць у Kodi</string>
-    <string name="kore_not_found">Усталяваць адсутную праграму Kore\?</string>
-    <string name="show_play_with_kodi_title">Паказаць опцыю \"Прайграць у Kodi\"</string>
+    <string name="play_with_kodi_title">Прайграць ў Kodi</string>
+    <string name="kore_not_found">Ўсталяваць адсутную праграму Kore?</string>
+    <string name="show_play_with_kodi_title">Паказаць опцыю \"Прайграць ў Kodi\"</string>
     <string name="show_play_with_kodi_summary">Паказаць опцыю прайгравання відэа праз медыяцэнтр Kodi</string>
     <string name="play_audio">Аўдыё</string>
     <string name="default_audio_format_title">Фармат аўдыё па змаўчанні</string>
@@ -70,7 +70,7 @@
     <string name="resume_on_audio_focus_gain_title">Узнавіць прайграванне</string>
     <string name="resume_on_audio_focus_gain_summary">Працягваць прайграванне пасля перапынкаў (напрыклад, тэлефонных званкоў)</string>
     <string name="download_dialog_title">Загрузіць</string>
-    <string name="show_next_and_similar_title">\"Наступнае\" и \"Прапанаванае\" відэа</string>
+    <string name="show_next_and_similar_title">\"Наступнае\" і \"Прапанаванае\" відэа</string>
     <string name="show_hold_to_append_title">Паказаць падказку \"Утрымлівайце, каб паставіць у чаргу\"</string>
     <string name="show_hold_to_append_summary">Паказаць падказку пры націсканні фонавай або ўсплывальнай кнопкі ў відэа \"Падрабязнасці:\"</string>
     <string name="unsupported_url">URL не падтрымліваецца</string>
@@ -227,7 +227,7 @@
 \nПалітыка прыватнасці NewPipe падрабязна тлумачыць, якія дадзеныя адпраўляюцца і захоўваюцца пры адпраўцы справаздачы аб збоях.</string>
     <string name="read_privacy_policy">Прачытаць палітыку</string>
     <string name="app_license_title">Ліцэнзія NewPipe</string>
-    <string name="app_license">NewPipe - гэта праграмнае забеспячэнне, свабоднае ад копілефта: вы можаце выкарыстоўваць, вывучаць, дзяліцца і паляпшаць яго па жаданні. У прыватнасці, вы можаце распаўсюджваць і/ці змяняць яго ў адпаведнасці з умовамі Агульнай грамадскай ліцэнзіі GNU, апублікаванай Фондам свабоднага праграмнага забеспячэння, альбо версіі 3 Ліцэнзіі, альбо (на ваш выбар) любой пазнейшай версіі.</string>
+    <string name="app_license">NewPipe - гэта праграмнае забеспячэнне, свабоднае ад копілефта: вы можаце выкарыстоўваць, вывучаць, дзяліцца і паляпшаць яго па жаданні. Ў прыватнасці, вы можаце распаўсюджваць і/ці змяняць яго ў адпаведнасці з умовамі Агульнай грамадскай ліцэнзіі GNU, апублікаванай Фондам свабоднага праграмнага забеспячэння, альбо версіі 3 Ліцэнзіі, альбо (на ваш выбар) любой пазнейшай версіі.</string>
     <string name="read_full_license">Прачытаць ліцэнзію</string>
     <string name="title_activity_history">Гісторыя</string>
     <string name="action_history">Гісторыя</string>
@@ -253,8 +253,8 @@
     <string name="play_queue_remove">Выдаліць</string>
     <string name="play_queue_stream_detail">Падрабязнасці</string>
     <string name="play_queue_audio_settings">Налады аўдыё</string>
-    <string name="hold_to_append">Утрымлівайце, каб дадаць у чаргу</string>
-    <string name="start_here_on_background">Пачаць адсюль у фоне</string>
+    <string name="hold_to_append">Утрымлівайце, каб дадаць ў чаргу</string>
+    <string name="start_here_on_background">Пачаць адсюль ў фоне</string>
     <string name="start_here_on_popup">Пачніце гуляць ва ўсплываючым акне</string>
     <string name="drawer_open">Адкрыць бакавую панэль</string>
     <string name="drawer_close">Зачыніць бакавую панэль</string>
@@ -262,16 +262,16 @@
     <string name="preferred_open_action_settings_summary">Пры адкрыцці спасылкі на кантэнт — %s</string>
     <string name="video_player">Відэаплэер</string>
     <string name="background_player">Фонавы плэер</string>
-    <string name="popup_player">Плэер у акне</string>
+    <string name="popup_player">Аконны прайгравальнік</string>
     <string name="always_ask_open_action">Заўсёды пытацца</string>
     <string name="preferred_player_fetcher_notification_title">Атрыманне звестак…</string>
     <string name="preferred_player_fetcher_notification_message">Загрузка запытанага кантэнту</string>
     <string name="create_playlist">Стварыць плэйліст</string>
     <string name="rename_playlist">Перайменаваць</string>
     <string name="name">Імя</string>
-    <string name="add_to_playlist">Дадаць у плэйліст</string>
-    <string name="set_as_playlist_thumbnail">Усталяваць як мініяцюру плэйліста</string>
-    <string name="bookmark_playlist">Дадаць плэйліст у закладкі</string>
+    <string name="add_to_playlist">Дадаць ў плэйліст</string>
+    <string name="set_as_playlist_thumbnail">Ўсталяваць як мініяцюру плэйліста</string>
+    <string name="bookmark_playlist">Дадаць плэйліст ў закладкі</string>
     <string name="unbookmark_playlist">Выдаліць закладку</string>
     <string name="delete_playlist_prompt">Выдаліць плэйліст\?</string>
     <string name="playlist_creation_success">Плэйліст створаны</string>
@@ -289,7 +289,7 @@
     <string name="enable_disposed_exceptions_summary">Прымусова паведамляць пра недастаўляемыя Rx-выключэнні па-за фрагментам або жыццёвым цыкле пасля выдалення</string>
     <string name="import_title">Імпарт</string>
     <string name="import_from">Імпарт з</string>
-    <string name="export_to">Экспарт у</string>
+    <string name="export_to">Экспарт ў</string>
     <string name="import_ongoing">Імпарт…</string>
     <string name="export_ongoing">Экспарт…</string>
     <string name="import_file_title">Імпарт файла</string>
@@ -299,17 +299,17 @@
     <string name="import_youtube_instructions">Імпарт падпісак YouTube з Google Takeout:
 \n
 \n1. Перайдзіце па гэтым URL: %1$s
-\n2. Увайдзіце, калі вас папросяць
+\n2. Ўвайдзіце, калі вас папросяць
 \n3. Націсніце на «Усе дадзеныя ўключаны», затым на «Адмяніць выбар усіх», затым выберыце толькі «падпіскі» і націсніце «ОК»
 \n4. Націсніце на «Наступны крок», а затым на «Стварыць экспарт»
 \n5. Націсніце на кнопку «Спампаваць» пасля таго, як яна з\'явіцца
 \n6. Пстрыкніце ФАЙЛ ІМПАРТУВАЦЬ ніжэй і выберыце спампаваны файл .zip
 \n7. [Калі імпарт .zip не ўдаецца] Распакуйце файл .csv (звычайна ў раздзеле \"YouTube і YouTube Music/subscriptions/subscriptions.csv\"), націсніце ФАЙЛ ІМПАРТУВАЦЬ ніжэй і выберыце выняты файл CSV</string>
-    <string name="import_soundcloud_instructions">Імпарт падпісак з SoundCloud набраўшы альбо URL, альбо ваш ID: 
-\n 
-\n1. Уключыце \"рэжым працоўнага стала\" у браўзэры (сайт недаступны на тэлефоне) 
-\n2. Перайдзіце на: %1$s 
-\n3. Увайдзіце, калі неабходна 
+    <string name="import_soundcloud_instructions">Імпарт падпісак з SoundCloud набраўшы альбо URL, альбо ваш ID:
+\n
+\n1. Ўключыце \"рэжым працоўнага стала\" ў браўзэры (сайт недаступны на тэлефоне)
+\n2. Перайдзіце на: %1$s
+\n3. Увайдзіце, калі неабходна
 \n4. Скапіруйце адрас з адраснага радка.</string>
     <string name="import_soundcloud_instructions_hint">вашID, soundcloud.com/вашID</string>
     <string name="import_network_expensive_warning">Гэтае дзеянне можа выклікаць вялікі расход трафіку. 
@@ -322,7 +322,7 @@
     <string name="skip_silence_checkbox">Прапускаць цішыню</string>
     <string name="playback_step">Крок</string>
     <string name="playback_reset">Скід</string>
-    <string name="start_accept_privacy_policy">У адпаведнасці з Агульным рэгламентам па абароне дадзеных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Калі ласка, уважліва азнаёмцеся з ёй.
+    <string name="start_accept_privacy_policy">Ў адпаведнасці з Агульным рэгламентам па абароне дадзеных ЕС (GDPR), звяртаем вашу ўвагу на палітыку прыватнасці NewPipe. Калі ласка, уважліва азнаёмцеся з ёй.
 \nВам неабходна прыняць яе ўмовы, каб адправіць нам справаздачу пра памылку.</string>
     <string name="accept">Прыняць</string>
     <string name="decline">Адмовіцца</string>
@@ -331,8 +331,8 @@
     <string name="minimize_on_exit_title">Пры згортванні плэера</string>
     <string name="minimize_on_exit_summary">Дзеянне пры пераключэнні са стандартнага плэера на іншае прыкладанне — %s</string>
     <string name="minimize_on_exit_none_description">Нічога не рабіць</string>
-    <string name="minimize_on_exit_background_description">Згарнуць у фонавы плэер</string>
-    <string name="minimize_on_exit_popup_description">Плэер у акне</string>
+    <string name="minimize_on_exit_background_description">Згарнуць ў фонавы плэер</string>
+    <string name="minimize_on_exit_popup_description">Плэер ў акне</string>
     <string name="unsubscribe">Адпісацца</string>
     <string name="tab_choose">Абярыце ўкладку</string>
     <string name="settings_category_updates_title">Абнаўленні</string>
@@ -356,14 +356,14 @@
     <string name="missions_header_finished">Скончана</string>
     <string name="missions_header_pending">У чарзе</string>
     <string name="paused">прыпынена</string>
-    <string name="queued">у чарзе</string>
+    <string name="queued">дададзены ў чаргу</string>
     <string name="post_processing">постапрацоўка</string>
-    <string name="enqueue">Паставіць у чаргу</string>
+    <string name="enqueue">Дадаць ў чаргу</string>
     <string name="permission_denied">Дзеянне забаронена сістэмай</string>
     <string name="download_failed">Памылка загрузкі</string>
     <string name="generate_unique_name">Стварыць унікальнае імя</string>
     <string name="overwrite">Перазапісаць</string>
-    <string name="download_already_running">Загрузка з такім імем ужо выконваецца</string>
+    <string name="download_already_running">Загрузка з такім імем ўжо выконваецца</string>
     <string name="show_error">Паказаць тэкст памылкі</string>
     <string name="error_path_creation">Немагчыма стварыць папку прызначэння</string>
     <string name="error_file_creation">Немагчыма стварыць файл</string>
@@ -377,7 +377,7 @@
     <string name="stop">Спыніць</string>
     <string name="max_retry_msg">Максімум спробаў</string>
     <string name="max_retry_desc">Колькасць спробаў перад адменай загрузкі</string>
-    <string name="pause_downloads_on_mobile">Перапыніць у платных сетках</string>
+    <string name="pause_downloads_on_mobile">Перапыніць ў платных сетках</string>
     <string name="pause_downloads_on_mobile_desc">Карысна пры пераключэнні на мабільную сетку, хоць некаторыя загрузкі не могуць быць прыпыненыя</string>
     <string name="events">Падзеі</string>
     <string name="conferences">Канферэнцыі</string>
@@ -394,10 +394,10 @@
     <string name="settings_category_clear_data_title">Ачысціць дадзеныя</string>
     <string name="watch_history_states_deleted">Пазіцыі прайгравання выдалены</string>
     <string name="missing_file">Файл перамешчаны ці выдалены</string>
-    <string name="overwrite_unrelated_warning">Файл з такім імем ужо існуе</string>
-    <string name="overwrite_finished_warning">Файл з такім імем ужо існуе</string>
+    <string name="overwrite_unrelated_warning">Файл з такім імем ўжо існуе</string>
+    <string name="overwrite_finished_warning">Файл з такім імем ўжо існуе</string>
     <string name="overwrite_failed">немагчыма перазапісаць файл</string>
-    <string name="download_already_pending">У чарзе ўжо ёсць загрузка з такім імем</string>
+    <string name="download_already_pending">Ў чарзе ўжо ёсць загрузка з такім імем</string>
     <string name="error_postprocessing_stopped">NewPipe была зачынена падчас працы над файлам</string>
     <string name="error_insufficient_storage">Скончылася вольнае месца на прыладзе</string>
     <string name="error_progress_lost">Прагрэс страчаны, так як файл быў выдалены</string>
@@ -408,8 +408,8 @@
     <string name="start_downloads">Пачаць загрузку</string>
     <string name="pause_downloads">Прыпыніць загрузку</string>
     <string name="downloads_storage_ask_title">Запытваць тэчку загрузкі</string>
-    <string name="downloads_storage_ask_summary">Вам будзе прапанавана ўказаць месца захавання кожнай загрузкі.
-\nУключыце сістэмны выбарнік тэчкі (SAF), калі вы хочаце загружаць файлы на знешнюю SD-картку</string>
+    <string name="downloads_storage_ask_summary">Вам будзе прапанавана указаць месца захавання кожнай загрузкі.
+\nЎключыце сістэмны выбарнік тэчкі (SAF), калі вы хочаце загружаць файлы на знешнюю SD-картку</string>
     <string name="downloads_storage_use_saf_title">Выкарыстоўвайце сродак выбару сістэмных тэчак (SAF)</string>
     <string name="downloads_storage_use_saf_summary">\'Storage Access Framework\' дазваляе загружаць на знешнюю SD-картку</string>
     <string name="drawer_header_description">Пераключыць службу, выбраную ў дадзены момант:</string>
@@ -426,7 +426,7 @@
     <string name="notification_action_1_title">Кнопка другога дзеяння</string>
     <string name="notification_action_0_title">Кнопка першага дзеяння</string>
     <string name="feed_groups_header_title">Групы каналаў</string>
-    <string name="systems_language">Як у сістэме</string>
+    <string name="systems_language">Як ў сістэме</string>
     <string name="app_language_title">Мова прылады</string>
     <string name="choose_instance_prompt">Выберыце экзэмпляр</string>
     <string name="delete_downloaded_files">Выдаліць загружаныя файлы</string>
@@ -442,15 +442,15 @@
     <string name="never">Ніколі</string>
     <string name="wifi_only">Толькі па Wi-Fi</string>
     <string name="show_original_time_ago_title">Паказаць арыгінальны час на элементах</string>
-    <string name="unmute">Уключыць гук</string>
+    <string name="unmute">Ўключыць гук</string>
     <string name="mute">Цішына</string>
-    <string name="enqueue_stream">Дадаць у чаргу</string>
-    <string name="enqueued">Даданае у чаргу</string>
+    <string name="enqueue_stream">Дадаць ў чаргу</string>
+    <string name="enqueued">Даданае ў чаргу</string>
     <string name="title_activity_play_queue">Чарга прайгравання</string>
     <string name="most_liked">Найбольш папулярнае</string>
     <string name="local">Лакальнае</string>
     <string name="recently_added">Нядаўна дададзенае</string>
-    <string name="no_playlist_bookmarked_yet">Няма закладак у плейлісце</string>
+    <string name="no_playlist_bookmarked_yet">Няма закладак ў плейлісце</string>
     <string name="select_a_playlist">Выберыце плэйліст</string>
     <string name="default_kiosk_page_summary">Кіёск па змаўчанні</string>
     <string name="done">Так</string>
@@ -478,7 +478,7 @@
     <string name="notification_action_4_title">Кнопка пятага дзеяння</string>
     <string name="notification_colorize_summary">Афарбоўваць апавяшчэнне асноўным колерам мініяцюры. Падтрымваецца не ўсімі прыладамі</string>
     <string name="notification_actions_at_most_three">У кампактным апавяшчэнні дасяжна не больш за тры дзеянні!</string>
-    <string name="notification_actions_summary">Дзеянні можна змяніць, націснуўшы на іх. Адзначце не больш за трох для адлюстравання ў кампактным апавяшчэнні</string>
+    <string name="notification_actions_summary">Адрэдагуйце кожнае дзеянне апавяшчэння, націснуўшы на яго. Выберыце да трох з іх, якія будуць адлюстроўвацца ў кампактным апавяшчэнні, выкарыстоўваючы сцяжкі справа.</string>
     <string name="unsupported_url_dialog_message">Не ўдалося распазнаць URL-адрас. Адкрыць у іншай праграме\?</string>
     <string name="settings_category_player_notification_title">Апавяшченне плэера</string>
     <string name="notifications">Апавяшчэнні</string>
@@ -497,9 +497,9 @@
     <string name="feed_group_dialog_empty_selection">Падпіскі не выбраны</string>
     <string name="feed_oldest_subscription_update">Апошняе абнаўленне: %s</string>
     <string name="auto_device_theme_title">Аўтаматычна (тэма прылады)</string>
-    <string name="night_theme_summary">Выберыце ўлюбёную начную тэму - %s</string>
+    <string name="night_theme_summary">Выберыце любімую начную тэму - %s</string>
     <string name="description_select_enable">Дазвол вылучэння тэксту ў апісанні</string>
-    <string name="select_night_theme_toast">Ніжэй вы можаце абраць улюбёную начную тэму</string>
+    <string name="select_night_theme_toast">Вы можаце выбраць сваю любімую начную тэму ніжэй</string>
     <string name="night_theme_available">Гэта опцыя даступна толькі тады, калі %s будзе выбранай тэмаю</string>
     <string name="download_has_started">Загрузка пачалась</string>
     <string name="notifications_disabled">Апавяшчэнні адключаныя</string>
@@ -519,7 +519,7 @@
     <string name="open_with">Адкрыць з дапамогай</string>
     <string name="night_theme_title">Начная тэма</string>
     <string name="open_website_license">Адкрыць вэб-сайт</string>
-    <string name="description_select_note">Цяпер Вы можаце вылучаць тэкст у апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мігацець, а спасылкі могуць быць недаступныя для націскання.</string>
+    <string name="description_select_note">Цяпер Вы можаце вылучаць тэкст ў апісанні. Звярніце ўвагу, што ў рэжыме вылучэння старонка можа мігацець, а спасылкі могуць быць недаступныя для націскання.</string>
     <string name="start_main_player_fullscreen_title">Запусціць галоўны прайгравальнік у поўнаэкранным рэжыме</string>
     <string name="show_channel_details">Паказаць дэталі канала</string>
     <string name="low_quality_smaller">Нізкая якасць (менш)</string>
@@ -574,14 +574,14 @@
         <item quantity="many">Выдалена %1$s зазагрузак</item>
         <item quantity="other">Выдалена %1$s зазагрузак</item>
     </plurals>
-    <string name="delete_downloaded_files_confirm">Выдаліць усе загружаныя файлы з дыска\?</string>
+    <string name="delete_downloaded_files_confirm">Выдаліць ўсе загружаныя файлы з дыска?</string>
     <plurals name="minutes">
         <item quantity="one">%d хвіліна</item>
         <item quantity="few">%d хвіліны</item>
         <item quantity="many">%d хвілінаў</item>
         <item quantity="other">%d хвілінаў</item>
     </plurals>
-    <string name="progressive_load_interval_summary">Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (у цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку</string>
+    <string name="progressive_load_interval_summary">Змяніць памер інтэрвалу загрузкі прагрэсіўнага змесціва (ў цяперашні час %s). Меншае значэнне можа паскорыць іх першапачатковую загрузку</string>
     <string name="show_description_summary">Выключыце, каб схаваць апісанне відэа і дадатковую інфармацыю</string>
     <string name="local_search_suggestions">Прапановы лакальнага пошуку</string>
     <string name="settings_category_player_notification_summary">Наладзіць апавяшчэнне аб бягучым прайграванні патоку</string>
@@ -608,7 +608,7 @@
     <string name="msg_calculating_hash">Разлік хэша</string>
     <string name="recaptcha_solve">Вырашана</string>
     <string name="playlist_no_uploader">Створана аўтаматычна (запампавальнік не знойдзены)</string>
-    <string name="duplicate_in_playlist">Плэйлісты, якія пазначаны шэрым, ужо ўтрымліваюць гэты элемент.</string>
+    <string name="duplicate_in_playlist">Плэйлісты, якія пазначаны шэрым, ўжо ўтрымліваюць гэты элемент.</string>
     <plurals name="new_streams">
         <item quantity="one">%s новы стрым</item>
         <item quantity="few">%s новыя стрымы</item>
@@ -616,8 +616,8 @@
         <item quantity="other">%s новых стрымаў</item>
     </plurals>
     <string name="comments_tab_description">Каментарыі</string>
-    <string name="enqueue_next_stream">У чаргу далей</string>
-    <string name="enqueued_next">У чарзе наступны</string>
+    <string name="enqueue_next_stream">Ў чаргу далей</string>
+    <string name="enqueued_next">Ў чарзе наступны</string>
     <string name="loading_stream_details">Загрузка звестак аб стрыме…</string>
     <string name="processing_may_take_a_moment">Апрацоўка... Можа заняць некаторы час</string>
     <string name="playlist_add_stream_success_duplicate">Дублікат дададзены %d раз</string>
@@ -650,7 +650,7 @@
     <string name="checking_updates_toast">Праверка абнаўленняў…</string>
     <string name="remove_duplicates_title">Выдаліць дублікаты\?</string>
     <string name="remove_duplicates">Выдаліць дублікаты</string>
-    <string name="remove_duplicates_message">Вы хочаце выдаліць усе паўтаральныя стрымы ў гэтым плэйлісце\?</string>
+    <string name="remove_duplicates_message">Вы хочаце выдаліць ўсе паўтаральныя стрымы ў гэтым плэйлісце?</string>
     <string name="feed_new_items">Новыя элементы стужкі</string>
     <plurals name="feed_group_dialog_selection_count">
         <item quantity="one">%d выбраны</item>
@@ -687,12 +687,12 @@
     <string name="settings_category_feed_title">Стужка</string>
     <string name="feed_update_threshold_summary">Час пасля апошняга абнаўлення, перш чым падпіска лічыцца састарэлай — %s</string>
     <string name="feed_load_error">Памылка загрузкі стужкі</string>
-    <string name="feed_load_error_terminated">Уліковы запіс аўтара быў спынены.
-\nNewPipe не зможа загрузіць гэты канал у будучыні.
-\nВы хочаце адмовіцца ад падпіскі на гэты канал\?</string>
+    <string name="feed_load_error_terminated">Ўліковы запіс аўтара быў спынены.
+\nNewPipe не зможа загрузіць гэты канал ў будучыні.
+\nВы хочаце адмовіцца ад падпіскі на гэты канал?</string>
     <string name="feed_load_error_fast_unknown">Рэжым хуткай загрузкі стужкі не дае дадатковай інфармацыі аб гэтым.</string>
     <string name="feed_use_dedicated_fetch_method_title">Атрымлівайце са спецыяльнага канала, калі ён даступны</string>
-    <string name="feed_use_dedicated_fetch_method_enable_button">Уключыць хуткі рэжым</string>
+    <string name="feed_use_dedicated_fetch_method_enable_button">Ўключыць хуткі рэжым</string>
     <string name="metadata_category">Катэгорыя</string>
     <string name="metadata_tags">Тэгі</string>
     <string name="metadata_licence">Ліцэнзія</string>
@@ -700,7 +700,7 @@
     <string name="metadata_privacy_unlisted">Не ў спісе</string>
     <string name="metadata_privacy_private">Прыватная</string>
     <string name="enumeration_comma">,</string>
-    <string name="toggle_all">Пераключыць усё</string>
+    <string name="toggle_all">Пераключыць ўсё</string>
     <string name="streams_not_yet_supported_removed">Стрымы, якія яшчэ не падтрымліваюцца загрузчыкам, не адлюстроўваюцца</string>
     <string name="detail_sub_channel_thumbnail_view_description">Мініяцюра аватара канала</string>
     <string name="video_detail_by">Аўтар: %s</string>
@@ -729,7 +729,7 @@
     <string name="account_terminated">Уліковы запіс спынены</string>
     <string name="service_provides_reason">%s дае наступную прычыну:</string>
     <string name="featured">Рэкамендаваны</string>
-    <string name="metadata_privacy_internal">Унутраная</string>
+    <string name="metadata_privacy_internal">Ўнутраная</string>
     <string name="feed_show_watched">Цалкам прагледзеў</string>
     <string name="paid_content">Гэты кантэнт даступны толькі для аплачаных карыстальнікаў, таму NewPipe не можа яго трансляваць або спампоўваць.</string>
     <string name="feed_use_dedicated_fetch_method_summary">Даступны ў некаторых службах, звычайна нашмат хутчэй, але можа вяртаць абмежаваную колькасць элементаў і часта няпоўную інфармацыю (напрыклад, без працягласці, тыпу элемента, без актыўнага стану)</string>
@@ -739,7 +739,7 @@
     <string name="no_app_to_open_intent">Ніякая праграма на вашай прыладзе не можа адкрыць гэта</string>
     <string name="progressive_load_interval_exoplayer_default">Стандартнае значэнне ExoPlayer</string>
     <string name="feed_show_partially_watched">Часткова прагледжана</string>
-    <string name="feed_use_dedicated_fetch_method_help_text">Як вы думаеце, загрузка корму адбываецца занадта павольна\? Калі так, паспрабуйце ўключыць хуткую загрузку (гэта можна змяніць у наладах або націснуўшы кнопку ніжэй).
+    <string name="feed_use_dedicated_fetch_method_help_text">Як вы думаеце, загрузка корму адбываецца занадта павольна? Калі так, паспрабуйце ўключыць хуткую загрузку (гэта можна змяніць ў наладах або націснуўшы кнопку ніжэй).
 \n
 \nNewPipe прапануе дзве стратэгіі загрузкі корму:
 \n• Атрыманне ўсяго канала падпіскі павольнае, але поўнае.
@@ -775,8 +775,8 @@
     <string name="audio_track_type_original">арыгінальны</string>
     <string name="audio_track_type_dubbed">дубляваны</string>
     <string name="audio_track_type_descriptive">апісальны</string>
-    <string name="audio_track_present_in_video">Гукавая дарожка ўжо павінна прысутнічаць у гэтай плыні</string>
-    <string name="use_exoplayer_decoder_fallback_summary">Уключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string>
+    <string name="audio_track_present_in_video">Гукавая дарожка ўжо павінна прысутнічаць ў гэтай плыні</string>
+    <string name="use_exoplayer_decoder_fallback_summary">Ўключыце гэту опцыю, калі ў вас ёсць праблемы з ініцыялізацыяй дэкодэра, якая вяртаецца да дэкодэраў з больш нізкім прыярытэтам, калі ініцыялізацыя асноўных дэкодэраў не ўдаецца. Гэта можа прывесці да нізкай прадукцыйнасці прайгравання, чым пры выкарыстанні асноўных дэкодэраў</string>
     <string name="settings_category_exoplayer_summary">Кіраванне некаторымі наладамі ExoPlayer. Каб гэтыя змены ўступілі ў сілу, патрабуецца перазапуск гульца</string>
     <string name="always_use_exoplayer_set_output_surface_workaround_summary">Гэты абыходны шлях вызваляе і паўторна стварае відэакодэкі, калі адбываецца змяненне паверхні, замест таго, каб усталёўваць паверхню непасрэдна для кодэка. ExoPlayer ужо выкарыстоўваецца на некаторых прыладах з гэтай праблемай, гэты параметр мае ўплыў толькі на прыладах з Android 6 і вышэй
 \n
@@ -820,7 +820,7 @@
     <string name="video_details_list_item">- %1$s: %2$s</string>
     <string name="main_tabs_position_summary">Перамясціць селектар галоўнай укладкі ўніз</string>
     <string name="no_live_streams">Няма жывых трансляцый</string>
-    <string name="image_quality_summary">Выберыце якасць выявы і ці спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне дадзеных і памяці. Змены ачышчаюць кэш малюнкаў як у памяці, так і на дыску - %s</string>
+    <string name="image_quality_summary">Выберыце якасць выявы і ці спампоўваць выявы ўвогуле, каб паменшыць выкарыстанне дадзеных і памяці. Змены ачышчаюць кэш малюнкаў як ў памяці, так і на дыску - %s</string>
     <string name="play">Прайграць</string>
     <string name="more_options">Іншыя опцыі</string>
     <string name="metadata_thumbnails">Мініяцюры</string>
@@ -830,4 +830,13 @@
     <string name="channel_tab_channels">Каналы</string>
     <string name="previous_stream">Папярэдні стрым</string>
     <string name="channel_tab_livestreams">Жывая трансляцыя</string>
+    <plurals name="replies">
+        <item quantity="one">%s адказ</item>
+        <item quantity="few">%s адказы</item>
+        <item quantity="many">%s адказаў</item>
+        <item quantity="other">%s адказаў</item>
+    </plurals>
+    <string name="show_more">Паказаць больш</string>
+    <string name="show_less">Паказаць менш</string>
+    <string name="notification_actions_summary_android13">Адрэдагуйце кожнае дзеянне апавяшчэння, націснуўшы на яго. Першыя тры дзеянні (прайграванне/паўза, папярэдняе і наступнае) задаюцца сістэмай і не могуць быць зменены.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 11fe2c2c8..4f27d60ce 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -297,7 +297,7 @@
     <string name="notification_scale_to_square_image_title">থাম্বনেল ১:১ অনুপাতে সেট করো</string>
     <string name="systems_language">সিস্টেম ডিফল্ট</string>
     <string name="bookmark_playlist">প্লেলিস্ট বুকমার্ক করুন</string>
-    <string name="feed_use_dedicated_fetch_method_title">"যখন পর্যাপ্ত  নিবেদিত ফিড থেকে ডাটা সংগ্রহ করুন"</string>
+    <string name="feed_use_dedicated_fetch_method_title">যখন পর্যাপ্ত নিবেদিত ফিড থেকে ডাটা সংগ্রহ করুন</string>
     <string name="feed_update_threshold_option_always_update">সবসময় হালনগাদ করুন</string>
     <string name="feed_update_threshold_summary">শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s</string>
     <string name="feed_update_threshold_title">ফিড হালনাগাদ প্রবেশস্থল</string>
@@ -317,11 +317,11 @@
     <string name="feed_groups_header_title">চ্যানেল গ্রুপ</string>
     <plurals name="days">
         <item quantity="one">%d দিন</item>
-        <item quantity="other">"%d   দিন"</item>
+        <item quantity="other">%d দিন</item>
     </plurals>
     <plurals name="hours">
         <item quantity="one">%d ঘন্টা</item>
-        <item quantity="other">"%d  ঘন্টা"</item>
+        <item quantity="other">%d ঘন্টা</item>
     </plurals>
     <plurals name="minutes">
         <item quantity="one">%d মিনিট</item>
diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml
index b2eacd22a..cb5c0e595 100644
--- a/app/src/main/res/values-bn-rIN/strings.xml
+++ b/app/src/main/res/values-bn-rIN/strings.xml
@@ -275,7 +275,7 @@
     <string name="website_encouragement">নিউ পাইপ ওয়েব সাইট এ যান বিস্তারিত বিবরণ ও খবর এর জন্য</string>
     <string name="more_than_100_videos">১০০+ ভিডিও</string>
     <plurals name="listening">
-        <item quantity="one">"%s  শ্রোতা"</item>
+        <item quantity="one">%s শ্রোতা</item>
         <item quantity="other">%s শ্রোতা গন</item>
     </plurals>
     <string name="description_tab_description">বিবরণ</string>
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index a0dbc0b36..6f4724f3b 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -151,7 +151,7 @@
     <string name="invalid_file">El fitxer no existeix o bé no teniu permisos de lectura/escriptura</string>
     <string name="file_name_empty_error">El nom del fitxer no pot estar en blanc</string>
     <string name="error_occurred_detail">S\'ha produït un error: %1$s</string>
-    <string name="error_report_button_text">Informeu de l\'error per correu electrònic</string>
+    <string name="error_report_button_text">Informeu per correu electrònic</string>
     <string name="error_snackbar_message">S\'han produït alguns errors.</string>
     <string name="error_snackbar_action">Informe</string>
     <string name="what_device_headline">Informació:</string>
@@ -561,7 +561,7 @@
     <string name="notification_action_shuffle">Mescla</string>
     <string name="notification_action_repeat">Repeteix</string>
     <string name="notification_actions_at_most_three">El màxim d\'accions que poden aparèixer en una notificació compacta és de tres!</string>
-    <string name="notification_actions_summary">Editeu cada acció de la notificació tocant el botó corresponent. Podeu seleccionar-ne fins a tres, que es mostraran a les notificacions en format compacte</string>
+    <string name="notification_actions_summary">Editeu cada acció de la notificació tocant el botó corresponent. Podeu seleccionar-ne fins a tres, que es mostraran a les notificacions en format compacte.</string>
     <string name="notification_action_4_title">Cinquè botó d\'acció</string>
     <string name="notification_action_3_title">Quart botó d\'acció</string>
     <string name="notification_action_2_title">Tercer botó d\'acció</string>
@@ -692,9 +692,26 @@
     <string name="unknown_quality">Cualitat desconeguda</string>
     <string name="sort">Ordenar</string>
     <string name="settings_category_player_notification_summary">Configura la notificació de reproducció actual.</string>
-    <string name="progressive_load_interval_summary">Canvia la mida de l\'interval de càrrega (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo. Els canvis requereixen un reinici del jugador.</string>
+    <string name="progressive_load_interval_summary">Canvia la mida de l\'interval de càrrega en continguts progressius (actualment %s). Un valor inferior pot accelerar la càrrega inicial del vídeo.</string>
     <string name="ignore_hardware_media_buttons_title">Ignora els esdeveniments dels botons de reproducció físics</string>
     <string name="ignore_hardware_media_buttons_summary">Útil, per exemple, si feu servir uns auriculars amb els botons físicament trencats</string>
     <string name="left_gesture_control_summary">Trieu un gest per la part esquerra de la pantalla</string>
     <string name="progressive_load_interval_title">Mida de l\'interval de càrrega de reproducció</string>
+    <string name="left_gesture_control_title">Acció de gest esquerra</string>
+    <string name="notification_actions_summary_android13">Editeu cada acció de notificació de sota tocant-la. Les tres primeres accions (reproduir/pausa, anterior i següent) són establertes pel sistema i no es poden personalitzar.</string>
+    <string name="right_gesture_control_summary">Tria un gest per a la meitat dreta del reproductor</string>
+    <string name="right_gesture_control_title">Acció del gest dret</string>
+    <string name="brightness">Brillantor</string>
+    <string name="volume">Volum</string>
+    <string name="none">Cap</string>
+    <string name="main_tabs_position_summary">Mou el selector de pestanya principal a la part inferior</string>
+    <string name="main_tabs_position_title">Posició de les pestanyes principals</string>
+    <string name="prefer_descriptive_audio_title">Prefereix àudio descriptiu</string>
+    <string name="prefer_original_audio_summary">Seleccioneu la pista d\'àudio original independentment de l\'idioma</string>
+    <string name="prefer_original_audio_title">Prefereix l\'àudio original</string>
+    <string name="fast_mode">Mode ràpid</string>
+    <string name="loading_metadata_title">Carregant Metadades…</string>
+    <string name="prefer_descriptive_audio_summary">Seleccioneu una pista d\'àudio amb descripcions per a persones amb discapacitat visual si està disponible</string>
+    <string name="streams_notification_channel_name">Nous streams</string>
+    <string name="streams_notification_channel_description">Notificacions sobre nous streams per a subscripcions</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index df26383a4..3cfa5ef7b 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -16,7 +16,7 @@
     <string name="privacy_policy_encouragement">پڕۆژەی نیوپایپ زانیارییە تایبەتییەکانت بە وردی دەپارێزێت. هەروەها به‌رنامه‌كه‌ هیچ زانایارییەکت بەبێ ئاگاداری تۆ بەکارنابات.
 \n‫سیاسەتی تایبەتی نیوپایپ بە وردی ڕوونکردنەوەت دەداتێ لەسەر ئەو زانیاریانەی وەریاندەگرێت و بەکاریاندەبات.</string>
     <string name="download_to_sdcard_error_message">ناتوانرێت لە بیرگەی دەرەکیدا داببەزێنرێت . شوێنی فۆڵده‌ری دابه‌زاندنەکان ڕێکبخرێتەوە؟</string>
-    <string name="did_you_mean">ئایا مەبەستت ئه‌مه‌یه‌ \"%1$s\"؟</string>
+    <string name="did_you_mean">مەبەستت لە ئەمەیە ٪1$s ؟</string>
     <string name="feed_update_threshold_title">ماوەی نوێكردنه‌وه‌ی فیید</string>
     <string name="grid">هێڵەکی</string>
     <string name="auto_queue_summary">به‌رده‌وامبوون له‌ (به‌بێ دووباره‌كردنه‌وه‌) نۆبه‌تی کارپێکەر به‌پێی په‌خشی هاوشێوه‌</string>
@@ -33,7 +33,7 @@
     <string name="name">ناو</string>
     <string name="error_postprocessing_failed">چارەسەرکردن هه‌ره‌سی هێنا</string>
     <string name="minimize_on_exit_title">بچوکبوونەوە لەکاتی گۆڕینی به‌رنامه‌</string>
-    <string name="download_path_summary">فایلی ڤیدیۆ دابه‌زێنراوەکان لێرەدا هەڵدەگیرێن</string>
+    <string name="download_path_summary">فایلی ڤیدیۆ داگیراوەکان لێرەدا هەڵدەگیرێن</string>
     <string name="export_data_summary">هەناردە کردنی مێژوو ، بەژدارییه‌كان ، خشته‌لێدانه‌كان و ڕێكخستنه‌كان</string>
     <string name="use_inexact_seek_summary">بردنەپێشی ناتەواوی خێرا وا لە لێدەرەکە دەکات کە بەخێرایی شوێنەکە بگۆڕێت. بردنەپێشی ٥ یان ١٥ یان ٢٥ چرکەیی لەگەڵ ئەمەدا کارناکات</string>
     <string name="enable_disposed_exceptions_summary">سکاڵاکردن لەسەر نەگەیاندنی Rx ی پەسەندنەکرا لە دەرەوەی پارچە یان چالاکی لەدوای پوختەکردن</string>
@@ -90,7 +90,7 @@
     <string name="play_with_kodi_title">لێدان به‌ Kodi</string>
     <string name="error_unable_to_load_comments">ناتوانرێت لێدوانەکان باربکرێن</string>
     <string name="peertube_instance_add_help">بەستەری دۆخ دابنێ</string>
-    <string name="download_path_audio_summary">فایلی دەنگە دابه‌زێنراوەکان لێرەدا هەڵدەگیرێن</string>
+    <string name="download_path_audio_summary">فایلی دەنگە داگیراوەکان لێرەدا هەڵدەگیرێن</string>
     <string name="error_snackbar_message">ببورە، هەندێك کێشە ڕوویدا.</string>
     <string name="export_to">هەناردە کردن بۆ</string>
     <string name="settings_category_player_behavior_title">ڕەفتار</string>
@@ -105,13 +105,13 @@
     <string name="unbookmark_playlist">لادانی نیشانه‌كراو</string>
     <string name="tab_licenses">مۆڵەتەکان</string>
     <string name="subscription_update_failed">ناتوانرێت به‌ژداریكردنه‌كه‌ نوێبكرێته‌وه‌</string>
-    <string name="controls_background_title">پاشبنەما</string>
+    <string name="controls_background_title">پشت شاشە</string>
     <string name="search_no_results">بێ ئەنجامه‌</string>
     <string name="localization_changes_requires_app_restart">زمان دەگۆڕدرێت لەدوای داگیرساندنەوەی به‌رنامه‌كه‌</string>
     <string name="remove_watched">لادانی سەیرکراو</string>
     <string name="enable_playback_state_lists_summary">پیشاندانی نیشانەکەری شوێنی کارپێکەر لە خشتەکاندا</string>
     <string name="enable_playback_state_lists_title">شوێنەکان لە خشتەکاندا</string>
-    <string name="subscribed_button_title">به‌ژداریت</string>
+    <string name="subscribed_button_title">به‌ژداریتکرد</string>
     <string name="caption_setting_description">بەهۆی گۆڕانکاری لە شێوەی ژێرنووسکردنەکە. پێویستە به‌رنامه‌كه‌ دابگیرسێنیته‌وه‌</string>
     <plurals name="feed_group_dialog_selection_count">
         <item quantity="one">%d دیار کراوه‌</item>
@@ -132,8 +132,8 @@
         <item quantity="one">‫%s بینراو</item>
         <item quantity="other">‫%s بینراوان</item>
     </plurals>
-    <string name="pause">ڕاگرتن</string>
-    <string name="download_path_audio_dialog_title">فۆڵدەری دابه‌زاندنی فایله‌ دەنگییەکان هەڵبژێرە</string>
+    <string name="pause">وەستاندن</string>
+    <string name="download_path_audio_dialog_title">فۆڵدەری داگرتنی فایله‌ دەنگییەکان هەڵبژێرە</string>
     <string name="feed_create_new_group_button_title">نوێ</string>
     <string name="clear_views_history_title">سڕینەوەی مێژووی سەیرکراو</string>
     <string name="enable_playback_resume_title">بەردەوام بوونی کارپێکەر</string>
@@ -158,7 +158,7 @@
     <string name="play_all">لێدانی گشتی</string>
     <string name="invalid_source">هەمان فایل/بابەت بوونی نییە</string>
     <string name="start">دەستپێکردن</string>
-    <string name="subscribe_button_title">به‌ژداری</string>
+    <string name="subscribe_button_title">به‌ژداریکردن</string>
     <string name="show_play_with_kodi_title">بژاردەی ”لێدان بە Kodi“ پیشانبدرێت</string>
     <string name="tab_subscriptions">به‌ژدارییه‌كان</string>
     <string name="blank_page_summary">پەڕەی بەتاڵ</string>
@@ -199,12 +199,12 @@
     <string name="detail_sub_channel_thumbnail_view_description">وێنۆچکەی سه‌روێنه‌ی کەناڵ</string>
     <string name="import_settings">دەتەوێت ڕێکخستنەکانیش هاوردە بكرینه‌وه‌؟</string>
     <string name="no_player_found">هیچ لێدەرێکی ڤیدیۆیی نه‌دۆزرایه‌وه‌. ده‌ته‌وێت VLC دابمەزرێنیت؟</string>
-    <string name="download_path_title">فۆڵده‌ری دابه‌زاندنی ڤیدیۆ</string>
+    <string name="download_path_title">فۆڵده‌ری داگرتنی ڤیدیۆ</string>
     <string name="drawer_open">کردنەوەی پلیکانە</string>
     <string name="light_theme_title">ڕووناك</string>
     <string name="show_search_suggestions_summary">ئەو پێشنیازکراوانە هەڵبژێرە کە پیشان دەدرێن لەکاتی گەڕاندا</string>
     <string name="error_progress_lost">کردارەکە هه‌ره‌سی هێنا, چونکە ئەو فایله‌ سڕاوەتەوە</string>
-    <string name="controls_add_to_playlist_title">زیادکردن بۆ</string>
+    <string name="controls_add_to_playlist_title">زیادی بکە بۆ</string>
     <string name="no_subscribers">به‌ژداری نییه‌</string>
     <string name="peertube_instance_url_title">دۆخی پێرتووب</string>
     <string name="playlist_creation_success">خشتەلێدان سازکرا</string>
@@ -214,7 +214,7 @@
     <string name="infinite_videos">∞ ڤیدیۆ</string>
     <string name="use_inexact_seek_title">بەکارهێنانی بردنەپێشی ناتەواوی خێرا</string>
     <string name="error_occurred_detail">هەڵەیەک ڕوویدا : %1$s</string>
-    <string name="download_path_dialog_title">فۆڵده‌ری دابه‌زاندن بۆ فایلی ڤیدیۆکان هەڵبژێرە</string>
+    <string name="download_path_dialog_title">فۆڵده‌ری داگرتن بۆ فایلی ڤیدیۆکان هەڵبژێرە</string>
     <string name="channel_created_by">ساز کراوه‌ لەلایەن %s</string>
     <string name="users">بەکارهێنەران</string>
     <string name="content">بابەت</string>
@@ -228,7 +228,7 @@
     <string name="privacy_policy_title">سیاسەتی تایبەتی نیوپایپ</string>
     <string name="settings_category_downloads_title">دابه‌زاندن</string>
     <string name="feed_use_dedicated_fetch_method_disable_button">ناكاراکردنی دۆخی خێرا</string>
-    <string name="open_in_browser">كردنه‌وه‌ له‌ وێبگه‌ر</string>
+    <string name="open_in_browser">ئەم بڕگەی پێڕستە ڤیدیۆیەک یان ستریمێکی دەنگی دەکاتەوە لە وێبگەڕێکدا</string>
     <string name="error_http_no_content">ڕاژەکە هیچ داتایەک نانێرێت</string>
     <string name="watch_history_states_deleted">شوێنی کارپێکراوەکان سڕانەوە</string>
     <string name="app_update_notification_channel_description">پەیامەکانی وەشانە نوێیەکانی نیوپایپ</string>
@@ -261,7 +261,7 @@
         <item quantity="other">%d ڕۆژان</item>
     </plurals>
     <string name="rename_playlist">ناولێنانه‌وه‌</string>
-    <string name="download">دابه‌زاندن</string>
+    <string name="download">داگرتن</string>
     <string name="ok">باشە</string>
     <string name="metadata_cache_wipe_title">سڕینه‌وه‌ی پاشماوەی مێتاداتا</string>
     <string name="error_download_resource_gone">ناتوانرێت ئەمه‌ داببه‌زێنرێته‌وه‌</string>
@@ -326,7 +326,7 @@
     <string name="default_resolution_title">قه‌باره‌ی بنەڕەتی</string>
     <string name="minimize_on_exit_popup_description">بچووککردنەوە بۆ پەنجەرە</string>
     <string name="songs">گۆرانییەکان</string>
-    <string name="controls_download_desc">دابه‌زاندنی فایلی پەخش</string>
+    <string name="controls_download_desc">داگرتنی فایلی پەخش</string>
     <string name="list_view_mode">شێوازی پیشاندانی خشتە</string>
     <string name="peertube_instance_add_title">زیادکردنی دۆخ</string>
     <string name="accept">پەسەند</string>
@@ -342,7 +342,7 @@
     <string name="main_page_content">بابەتی پەڕەی سەرەکی</string>
     <string name="feed_group_dialog_select_subscriptions">دیار کردنی بەژدارییەکان</string>
     <string name="import_file_title">هاورده‌كردنی فایل</string>
-    <string name="download_path_audio_title">فۆڵده‌ری دابه‌زاندنی ده‌نگ</string>
+    <string name="download_path_audio_title">فۆڵده‌ری داگرتنی ده‌نگ</string>
     <string name="use_external_video_player_summary">هه‌ندێك له‌ قه‌باره‌كان ده‌نگیان تێدا نامێنێته‌وه‌</string>
     <string name="events">ڕووداوەکان</string>
     <string name="detail_uploader_thumbnail_view_description">وێنۆچکەی کەسی بەرزکەرەوە</string>
@@ -390,7 +390,7 @@
     <string name="start_here_on_background">دەستپێکردنی لێدان لە پاشبنەماوە</string>
     <string name="msg_name">ناوفایل</string>
     <string name="set_as_playlist_thumbnail">دانان لەسەر وێنۆچکەی خشتەلێدان</string>
-    <string name="title_activity_about">دەربارەی نیوپایپ</string>
+    <string name="title_activity_about">دەربارەی NewPipe</string>
     <string name="add_to_playlist">زیادکردن بۆ خشتەلێدان</string>
     <string name="unknown_content">(نەزانراو)</string>
     <string name="app_language_title">زمانی به‌رنامه‌</string>
@@ -469,7 +469,7 @@
     <string name="metadata_cache_wipe_summary">سڕینەوەی پاشماوەی هەموو ماڵپه‌ڕه‌كان</string>
     <string name="kore_not_found">بەرنامەکە نه‌دۆزرایه‌وه‌. دابمه‌زرێت؟</string>
     <string name="peertube_instance_add_fail">ناتوانرێ پشتگیری دۆخەکە بکرێ</string>
-    <string name="install">دامەزراندن</string>
+    <string name="install">دابەزاندن</string>
     <string name="videos_string">ڤیدیۆکان</string>
     <string name="unsupported_url">بەستەرەکە پشتگیری نەکراوە</string>
     <string name="playback_pitch">قیڕ</string>
@@ -484,7 +484,7 @@
     <string name="enable_queue_limit_desc">لەیەک کاتدا تەنیا یەک بابەت دادەبەزێنرێت</string>
     <string name="restore_defaults_confirmation">دەتەوێت بگەڕێنرێتەوە بۆ شێوازی بنەڕەتی؟</string>
     <string name="pause_downloads">وەستاندنی دابەزاندنەکان</string>
-    <string name="tab_about">دەربارە</string>
+    <string name="tab_about">دەربارە و پرسیارەکان</string>
     <string name="show_comments_title">پیشاندانی لێدوانەکان</string>
     <string name="start_accept_privacy_policy">بۆ جێبەجێکردنی فرمانەکان لەگەڵ یاسای پاراستنی داتای گشتی ئەوروپیدا (GDPR) , ئێمە سەرنجت ڕادەکێشین بۆ سیاسەتە تایبەتییەکانی نیوپایپ. تکایە بەئاگادارییەوە بیخوێنەره‌وە.
 \nپێویستە په‌سه‌ندی بکەیت بۆ ناردنی سکاڵاکانت.</string>
@@ -502,7 +502,7 @@
     <string name="feed_update_threshold_summary">کاتی دوای دواین نوێکردنەوە پێش بەژداربوون ڕەچاوکراوە — %s</string>
     <string name="download_to_sdcard_error_title">بیرگەی دەرەکی بەردەست نییە</string>
     <string name="enable_playback_resume_summary">گێڕانەوەی کارپێکەر بۆ شوێنی پێشووتر</string>
-    <string name="cancel">پاشگه‌زبوونه‌وه‌</string>
+    <string name="cancel">هەڵوەشاندنەوه</string>
     <string name="tracks">تراکەکان</string>
     <string name="play_queue_audio_settings">ڕێکخستنەکانی دەنگ</string>
     <string name="downloads_storage_ask_summary">پرست پێ دەکرێت بۆ شوێنی دابەزاندنی هەر بابەتێک.
@@ -541,7 +541,7 @@
     <string name="notification_action_shuffle">تێکەڵکردن</string>
     <string name="notification_action_repeat">دووبارە</string>
     <string name="notification_actions_at_most_three">دەتوانیت تا سێ كردار دیار بكه‌یت تا پیشان بدرێن له‌ پەیامەکەدا!</string>
-    <string name="notification_actions_summary">ده‌ستكاری هه‌ر یه‌كێك له‌م كردارانه‌ی خواره‌وه‌ بكه‌ له‌ڕێگه‌ی كرته‌ له‌سه‌ریان. ده‌توانیت تا زیاتر له‌ سێ دانه‌یان هه‌ڵبژێریت له‌ ڕێگای چوارگۆشه‌كانی لای ڕاسته‌وه‌یان، تا پیشان بدرێن له‌ پەیامەکاندا</string>
+    <string name="notification_actions_summary">دەستکاریکردنی هەر کردارێکی ئاگادارکەرەوە لە خوارەوە بە دەستلێدان. ۳- دانە هەڵبژێرە لە ڕێگەی بەکارهێنانی سندوقەبچوکەکە لای ڕاستەوە نیشان دەدرێت</string>
     <string name="notification_action_4_title">پێنجه‌م كرداری دوگمه‌</string>
     <string name="notification_action_3_title">چواره‌م كرداری دوگمه‌</string>
     <string name="notification_action_2_title">سێیه‌م كرداری دوگمه‌</string>
@@ -555,7 +555,7 @@
     <string name="show_meta_info_title">پیشاندانی زانیاری مێتا</string>
     <string name="show_description_summary">ناكارایبكه‌ بۆ شاردنه‌وه‌ی دیسکریپشن له‌سه‌ر ڤیدیۆ و زانیاری زیاتر</string>
     <string name="show_description_title">پیشاندانی دیسکریپشن</string>
-    <string name="night_theme_title">ڕووكاری شه‌و</string>
+    <string name="night_theme_title">ڕووكاری تاریک</string>
     <string name="notification_colorize_summary">ئه‌ندرۆید ڕه‌نگی پەیام دڵخواز ده‌كات به‌پێی ڕه‌نگی سه‌ره‌كی وێنۆچكه‌كه‌ ( ڕه‌چاوی ئه‌وه‌ بكه‌ كه‌ ئه‌م تایبه‌تمه‌ندییه‌ هه‌موو ئامێرێك ناگرێته‌وه‌ )</string>
     <string name="notification_colorize_title">ڕه‌نگكردنی پەیام</string>
     <string name="youtube_restricted_mode_enabled_summary">یوتوب ”دۆخی قه‌ده‌غه‌كراو” پێشكه‌ش ده‌كات كه‌ بابەتە نه‌شیاوه‌كان ده‌شارێته‌وه‌</string>
@@ -582,7 +582,7 @@
 \nجا ده‌ته‌وێت به‌ژداری لابده‌یت له‌م كه‌ناڵه‌؟</string>
     <string name="feed_load_error_account_info">ناتوانرێت فیید باربکرێت تا ً`%s` .</string>
     <string name="feed_load_error">هه‌ڵه‌ له‌ باركردنی فیید</string>
-    <string name="disable_media_tunneling_summary">ئه‌م تایبه‌تمه‌ندییه‌ كارابكه‌ گه‌ر ڕوونمای ڕه‌ش یاخوود جامبوونی کارپێکەرت ئه‌زموون كرد</string>
+    <string name="disable_media_tunneling_summary">ئەگەر تووشی شاشەی ڕەش یان لکەلکە بوویت لە کاتی پەخشکردنی ڤیدیۆدا، تونێلکردنی میدیا لەکاربخە.</string>
     <string name="metadata_privacy_internal">ناوەکی</string>
     <string name="metadata_privacy_private">تایبەتی</string>
     <string name="metadata_privacy_unlisted">خشتەنەکراو</string>
@@ -665,11 +665,11 @@
     <string name="detail_pinned_comment_view_description">لێدوانی هەڵواسراو</string>
     <string name="crash_the_player">کڕاش کردنی لێدەر</string>
     <string name="show_error_snackbar">پیشاندانی هەڵەی سناکباڕ</string>
-    <string name="no_appropriate_file_manager_message">هیچ ڕێکخەرێکی فایلی گونجاو نەدۆزرایەوە بۆ ئەم کردارە.
-\nتکایە ڕێکخەری فایلییەک دابمەزرێنە لۆ هەوڵدانی ناکاراکردنی \'%s\' لە ڕێکخستنەکانی دابەزاندندا.</string>
+    <string name="no_appropriate_file_manager_message">هیچ FileManager پەڕگەی گونجاو بۆ ئەم کردارە نەدۆزراوەتەوە.
+\nتکایە بەڕێوەبەری پەڕگەیەک دابمەزرێنە یان هەوڵبدە \'%s\' لە Settings بڕۆ Download لەکاربخە</string>
     <string name="leak_canary_not_available">LeakCanary بەردەست نییە</string>
-    <string name="no_appropriate_file_manager_message_android_10">هیچ ڕێکخەرێکی فایلی گونجاو نەدۆزرایەوە بۆ ئەم کردارە.
-\nتکایە ڕێکخەرێکی فایلی دابمەزرێنە کە گونجاوبێت لەگەڵ دەسەڵاتی گەیشتن بە بیرگە.</string>
+    <string name="no_appropriate_file_manager_message_android_10">هیچ FileManager گونجاو نەدۆزرایەوە بۆ ئەم کردارە.
+\nتکایە FileManager دابمەزرێنە کە گونجاوبێت لەگەڵ دەسەڵاتی گەیشتن بە بیرگە.</string>
     <string name="check_new_streams">پشکنین کردن بۆ پەخشی نوێ</string>
     <string name="enable_streams_notifications_title">پەیامەکانی پەخشە نوێیەکان</string>
     <string name="enable_streams_notifications_summary">پەیام بکرێم لەکاتی هەبوونی پەخشی نوێی بەژدارییەکان</string>
@@ -694,4 +694,12 @@
     <string name="percent">لەسەدا</string>
     <string name="semitone">نیمچەتەن</string>
     <string name="progressive_load_interval_exoplayer_default">بنەڕەتی ExoPlayer</string>
+    <string name="prefer_original_audio_summary">دیاریکردنی تراکی دەنگی ئەسڵی بێ گوێدانە زمانەکە</string>
+    <string name="notification_actions_summary_android13">دەستکاریکردنی هەر کردارێکی ئاگادارکەرەوە لە خوارەوە بە دەستلێدان. یەکەم سێ کردار (لێدان/وەستان، پێشوو و دواتر) لەلایەن سیستەمەکەوە دانراوە و ناتوانرێت دەستکاری بکرێت.</string>
+    <string name="prefer_descriptive_audio_title">پەسەند کردنی دەنگی وەسفکراو</string>
+    <string name="progressive_load_interval_summary">گۆڕینی قەبارەی ماوەی لۆد لەسەر ناوەڕۆکی پێشکەوتوو (ئێستا ٪s). بەهایەکی کەمتر لەوانەیە بارکردنی سەرەتا خێراتر بکات</string>
+    <string name="prefer_original_audio_title">پەسەندکردنی دەنگی ئەسڵی</string>
+    <string name="ignore_hardware_media_buttons_summary">بەسوودە، بۆ نموونە، ئەگەر هێدسێتێک بەکاربهێنیت لەگەڵ دوگمەی فیزیکی شکاو</string>
+    <string name="progressive_load_interval_title">قەبارەی نێوان بارکردنی پەخشکردن</string>
+    <string name="ignore_hardware_media_buttons_title">دوگمەی ڕووداوەکانی میدیای هاردوێر بەجێبهێڵە</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 5c3997d45..1a5b83e52 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -554,7 +554,7 @@
     <string name="notification_action_shuffle">Promíchat</string>
     <string name="notification_action_repeat">Opakovat</string>
     <string name="notification_actions_at_most_three">Do kompaktního oznámení lze vybrat nejvíce tři akce!</string>
-    <string name="notification_actions_summary">Upravte každou akci oznámení níže poklepáním. Pomocí zaškrtávacích políček vpravo vyberte až tři z nich, které se mají zobrazit v kompaktním oznámení</string>
+    <string name="notification_actions_summary">Upravte každou akci oznámení níže poklepáním. Pomocí zaškrtávacích políček vpravo vyberte až tři z nich, které se mají zobrazit v kompaktním oznámení.</string>
     <string name="notification_action_4_title">Páté akční tlačítko</string>
     <string name="notification_action_3_title">Čtvrté akční tlačítko</string>
     <string name="notification_action_2_title">Třetí akční tlačítko</string>
@@ -819,4 +819,12 @@
     <string name="channel_tab_channels">Kanály</string>
     <string name="previous_stream">Předchozí stream</string>
     <string name="channel_tab_livestreams">Živě</string>
+    <plurals name="replies">
+        <item quantity="one">%s odpověď</item>
+        <item quantity="few">%s odpovědi</item>
+        <item quantity="other">%s odpovědí</item>
+    </plurals>
+    <string name="show_more">Zobrazit více</string>
+    <string name="notification_actions_summary_android13">Upravte každou akci oznámení níže poklepáním. První tři akce (přehrání/pozastavení, předchozí a další) jsou nastaveny systémem a nemohou být přizpůsobeny.</string>
+    <string name="show_less">Zobrazit méně</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index afde293a6..f0ffba311 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -4,64 +4,64 @@
     <string name="upload_date_text">Udgivet den %1$s</string>
     <string name="no_player_found">Ingen streamafspiller blev fundet. Installér VLC\?</string>
     <string name="no_player_found_toast">Ingen streamafspiller fundet (du kan installere VLC for at afspille den).</string>
-    <string name="install">Installer</string>
-    <string name="cancel">Annuller</string>
+    <string name="install">Installér</string>
+    <string name="cancel">Annullér</string>
     <string name="open_in_browser">Åbn i browser</string>
-    <string name="open_in_popup_mode">Åbn i pop op-tilstand</string>
+    <string name="open_in_popup_mode">Åbn i popup-tilstand</string>
     <string name="share">Del</string>
-    <string name="download">Download</string>
-    <string name="controls_download_desc">Download stream-fil</string>
+    <string name="download">Hent</string>
+    <string name="controls_download_desc">Hent stream-fil</string>
     <string name="search">Søg</string>
     <string name="settings">Indstillinger</string>
     <string name="did_you_mean">Mente du \"%1$s\"\?</string>
     <string name="share_dialog_title">Del med</string>
-    <string name="use_external_video_player_title">Benyt ekstern videoafspiller</string>
+    <string name="use_external_video_player_title">Brug ekstern videoafspiller</string>
     <string name="use_external_video_player_summary">Fjerner lyd ved nogle opløsninger</string>
     <string name="use_external_audio_player_title">Brug ekstern lydafspiller</string>
-    <string name="subscribe_button_title">Abonner</string>
+    <string name="subscribe_button_title">Abonnér</string>
     <string name="subscribed_button_title">Abonnerer</string>
-    <string name="unsubscribe">Afmeld abonnement</string>
-    <string name="channel_unsubscribed">Abonnement afmeldt</string>
+    <string name="unsubscribe">Afmeld</string>
+    <string name="channel_unsubscribed">Kanal afmeldt</string>
     <string name="subscription_change_failed">Kunne ikke ændre abonnement</string>
     <string name="subscription_update_failed">Kunne ikke opdatere abonnement</string>
     <string name="show_info">Vis info</string>
     <string name="tab_subscriptions">Abonnementer</string>
-    <string name="tab_bookmarks">Gemte spillelister</string>
-    <string name="tab_choose">Vælg fane</string>
+    <string name="tab_bookmarks">Gemte Playlister</string>
+    <string name="tab_choose">Vælg Fane</string>
     <string name="fragment_feed_title">Nyheder</string>
     <string name="controls_background_title">Baggrund</string>
-    <string name="controls_popup_title">Pop op</string>
+    <string name="controls_popup_title">Popup</string>
     <string name="controls_add_to_playlist_title">Føj til</string>
-    <string name="download_path_title">Mappe til download af video</string>
-    <string name="download_path_summary">Downloadede videoer gemmes her</string>
-    <string name="download_path_dialog_title">Angiv download-mappe for videofiler</string>
-    <string name="download_path_audio_title">Download-mappe for lydfiler</string>
-    <string name="download_path_audio_summary">Downloadede lydfiler gemmes her</string>
-    <string name="download_path_audio_dialog_title">Angiv download-mappe for lydfiler</string>
+    <string name="download_path_title">Lagringsmappe til videoer</string>
+    <string name="download_path_summary">Hentede videoer gemmes her</string>
+    <string name="download_path_dialog_title">Vælg lagringsmappe til videofiler</string>
+    <string name="download_path_audio_title">Lagringsmappe til lydfiler</string>
+    <string name="download_path_audio_summary">Hentede lydfiler gemmes her</string>
+    <string name="download_path_audio_dialog_title">Vælg lagringsmappe til lydfiler</string>
     <string name="default_resolution_title">Standardopløsning</string>
-    <string name="default_popup_resolution_title">Standardopløsning for pop op</string>
+    <string name="default_popup_resolution_title">Standardopløsning for popup</string>
     <string name="show_higher_resolutions_title">Vis højere opløsninger</string>
     <string name="show_higher_resolutions_summary">Kun nogle enheder kan afspille 2K-/4K-videoer</string>
     <string name="play_with_kodi_title">Afspil med Kodi</string>
-    <string name="kore_not_found">Installer manglede Kode-app\?</string>
+    <string name="kore_not_found">Installér manglende Kore-app?</string>
     <string name="show_play_with_kodi_title">Vis valgmuligheden \"Afspil med Kodi\"</string>
     <string name="show_play_with_kodi_summary">Vis en knap til at afspille en video via Kodi-mediecenteret</string>
     <string name="play_audio">Lyd</string>
     <string name="default_audio_format_title">Standardformat for lydfiler</string>
     <string name="default_video_format_title">Standardformat for videofiler</string>
     <string name="theme_title">Tema</string>
-    <string name="light_theme_title">Lyst</string>
-    <string name="dark_theme_title">Mørkt</string>
+    <string name="light_theme_title">Lys</string>
+    <string name="dark_theme_title">Mørk</string>
     <string name="black_theme_title">Sort</string>
-    <string name="popup_remember_size_pos_title">Husk størrelse og placering af pop op</string>
-    <string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af pop op-afspiller</string>
+    <string name="popup_remember_size_pos_title">Husk popup-egenskaber</string>
+    <string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af popup-afspiller</string>
     <string name="use_inexact_seek_title">Brug hurtig og upræcis søgning</string>
     <string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling slået til</string>
     <string name="thumbnail_cache_wipe_complete_notice">Billedcache slettet</string>
-    <string name="metadata_cache_wipe_title">Slet metadata-cachen</string>
-    <string name="metadata_cache_wipe_summary">Slet alle websidedata fra cachen</string>
+    <string name="metadata_cache_wipe_title">Slet metadata-cache</string>
+    <string name="metadata_cache_wipe_summary">Fjern alle cached websidedata</string>
     <string name="metadata_cache_wipe_complete_notice">Metadata-cache slettet</string>
-    <string name="auto_queue_title">Føj automatisk næste stream til køen</string>
+    <string name="auto_queue_title">Føj automatisk næste stream til kø</string>
     <string name="auto_queue_summary">Fortsæt en afspilningskø, der afsluttes (ikke-gentagende), ved at tilføje en lignende stream</string>
     <string name="show_search_suggestions_title">Søgeforslag</string>
     <string name="show_search_suggestions_summary">Vælg forslagene, der vises, når der søges</string>
@@ -71,63 +71,63 @@
     <string name="enable_watch_history_summary">Husk sete videoer</string>
     <string name="resume_on_audio_focus_gain_title">Fortsæt afspilning</string>
     <string name="resume_on_audio_focus_gain_summary">Fortsæt afspilning efter afbrydelser (fx telefonopkald)</string>
-    <string name="download_dialog_title">Download</string>
+    <string name="download_dialog_title">Hent</string>
     <string name="show_next_and_similar_title">Vis \'Næste\' og \'Lignende\' videoer</string>
     <string name="show_hold_to_append_title">Vis \"Hold for at sætte i kø\"-tip</string>
-    <string name="show_hold_to_append_summary">Vis et tip når der trykkes på baggrunds- eller pop op-knappen på siden med videodetaljer</string>
-    <string name="unsupported_url">Denne webadresse er ikke understøttet</string>
+    <string name="show_hold_to_append_summary">Vis tip, når du trykker på baggrunden eller popup-knappen i video \"Detaljer:\"</string>
+    <string name="unsupported_url">Ikke-understøttet URL</string>
     <string name="default_content_country_title">Standardland for indhold</string>
     <string name="content_language_title">Standardsprog for indhold</string>
     <string name="settings_category_player_title">Afspiller</string>
-    <string name="settings_category_player_behavior_title">Opførsel</string>
+    <string name="settings_category_player_behavior_title">Adfærd</string>
     <string name="settings_category_video_audio_title">Video og lyd</string>
     <string name="settings_category_history_title">Historik og cache</string>
     <string name="settings_category_appearance_title">Udseende</string>
     <string name="settings_category_debug_title">Fejlretning</string>
     <string name="settings_category_updates_title">Opdateringer</string>
     <string name="background_player_playing_toast">Afspiller i baggrunden</string>
-    <string name="popup_playing_toast">Afspiller i pop op-tilstand</string>
+    <string name="popup_playing_toast">Afspiller i popup-tilstand</string>
     <string name="content">Indhold</string>
     <string name="show_age_restricted_content_title">Vis aldersbegrænset indhold</string>
     <string name="duration_live">Live</string>
-    <string name="downloads">Downloads</string>
-    <string name="downloads_title">Downloads</string>
+    <string name="downloads">Hentet</string>
+    <string name="downloads_title">Hentet</string>
     <string name="error_report_title">Fejlrapport</string>
     <string name="all">Alle</string>
     <string name="channels">Kanaler</string>
-    <string name="playlists">Spillelister</string>
+    <string name="playlists">Playlister</string>
     <plurals name="videos">
         <item quantity="one">Én video</item>
         <item quantity="other">%s videoer</item>
     </plurals>
     <string name="tracks">Numre</string>
     <string name="users">Brugere</string>
-    <string name="disabled">Slået fra</string>
-    <string name="clear">Slet</string>
+    <string name="disabled">Deaktiveret</string>
+    <string name="clear">Ryd</string>
     <string name="best_resolution">Bedste opløsning</string>
     <string name="undo">Fortryd</string>
     <string name="file_deleted">Fil slettet</string>
-    <string name="play_all">Afspil alle</string>
+    <string name="play_all">Afspil Alle</string>
     <string name="always">Altid</string>
-    <string name="just_once">Kun én gang</string>
+    <string name="just_once">Kun Én Gang</string>
     <string name="file">Fil</string>
-    <string name="notification_channel_name">NewPipe notifikation</string>
+    <string name="notification_channel_name">NewPipe-notifikation</string>
     <string name="notification_channel_description">Notifikationer for NewPipes afspiller</string>
     <string name="app_update_notification_channel_name">Notifikation om opdatering af app</string>
     <string name="app_update_notification_channel_description">Notifikationer for nye NewPipe versioner</string>
     <string name="unknown_content">[Ukendt]</string>
     <string name="switch_to_background">Skift til baggrund</string>
-    <string name="switch_to_popup">Skift til pop op</string>
+    <string name="switch_to_popup">Skift til popup</string>
     <string name="switch_to_main">Skift til hovedafspiller</string>
-    <string name="import_data_title">Importer database</string>
-    <string name="export_data_title">Eksporter database</string>
+    <string name="import_data_title">Importér database</string>
+    <string name="export_data_title">Eksportér database</string>
     <string name="import_data_summary">Overskriver din nuværende historik, abonnementer, spillelister og (hvis det ønskes) indstillinger</string>
-    <string name="export_data_summary">Eksporter historik, abonnementer, spillelister og indstillinger</string>
-    <string name="clear_views_history_title">Slet visningshistorik</string>
+    <string name="export_data_summary">Eksportér historik, abonnementer, spillelister og indstillinger</string>
+    <string name="clear_views_history_title">Ryd visningshistorik</string>
     <string name="clear_views_history_summary">Sletter historikken over afspillede streams og afspilningspositionerne</string>
     <string name="delete_view_history_alert">Slet hele visningshistorikken\?</string>
     <string name="watch_history_deleted">Visningshistorikken blev slettet</string>
-    <string name="clear_search_history_title">Slet søgehistorik</string>
+    <string name="clear_search_history_title">Ryd søgehistorik</string>
     <string name="clear_search_history_summary">Sletter historikken for søgeord</string>
     <string name="delete_search_history_alert">Slet hele søgehistorikken\?</string>
     <string name="search_history_deleted">Søgehistorikken blev slettet</string>
@@ -367,12 +367,12 @@
     <string name="max_retry_desc">Maksimalt antal forsøg før downloaden opgives</string>
     <string name="pause_downloads_on_mobile">Afbryd på forbrugsafregnede netværk</string>
     <string name="pause_downloads_on_mobile_desc">Nyttigt ved skift til mobildata, selv om nogle downloads ikke kan sættes på pause</string>
-    <string name="peertube_instance_add_https_only">Kun HTTPS adresser understøttes</string>
+    <string name="peertube_instance_add_https_only">Kun HTTPS-URL\'er understøttes</string>
     <string name="peertube_instance_add_exists">Instansen findes allerede</string>
     <string name="peertube_instance_add_fail">Kunne ikke validere instansen</string>
-    <string name="peertube_instance_add_help">Skriv instansens adresse</string>
+    <string name="peertube_instance_add_help">Indtast instans-URL</string>
     <string name="peertube_instance_add_title">Tilføj instans</string>
-    <string name="peertube_instance_url_help">Find de instanserne du kan lide på %s</string>
+    <string name="peertube_instance_url_help">Find de instanser, du kan lide på %s</string>
     <string name="peertube_instance_url_summary">Vælg dine yndlings PeerTube-instanser</string>
     <string name="peertube_instance_url_title">PeerTube-instanser</string>
     <string name="autoplay_title">Afspil automatisk</string>
@@ -384,11 +384,11 @@
     <string name="show_comments_title">Vis kommentarer</string>
     <string name="notification_action_nothing">Ingenting</string>
     <string name="notification_action_repeat">Gentag</string>
-    <string name="notification_action_4_title">Femte handlingstast</string>
-    <string name="notification_action_3_title">Fjerde handlingstast</string>
-    <string name="notification_action_0_title">Første handlingstast</string>
-    <string name="notification_action_1_title">Anden handlingstast</string>
-    <string name="notification_action_2_title">Tredje handlingstast</string>
+    <string name="notification_action_4_title">Femte handlingsknap</string>
+    <string name="notification_action_3_title">Fjerde handlingsknap</string>
+    <string name="notification_action_0_title">Første handlingsknap</string>
+    <string name="notification_action_1_title">Anden handlingsknap</string>
+    <string name="notification_action_2_title">Tredje handlingsknap</string>
     <string name="search_showing_result_for">Viser resultater for: %s</string>
     <string name="open_with">Åbn med</string>
     <string name="leak_canary_not_available">LeakCanary er ikke tilgængelig</string>
@@ -426,42 +426,42 @@
     </plurals>
     <string name="delete_downloaded_files_confirm">Slet alle downloadede filer fra drevet\?</string>
     <string name="pause_downloads">Sæt downloads på pause</string>
-    <string name="start_main_player_fullscreen_title">Start hovedafspilleren i fuldskærmstilstand</string>
+    <string name="start_main_player_fullscreen_title">Start hovedafspiller i fuld skærm</string>
     <string name="no_dir_yet">Downloadmappe endnu ikke valgt. Vælg standardmappen nu</string>
-    <string name="auto_queue_toggle">Læg automatisk i kø</string>
-    <string name="settings_category_player_notification_summary">Konfigurer det spillende streams notifikation</string>
-    <string name="show_age_restricted_content_summary">Vis aldersbegrænset indhold (f.eks. 18+)</string>
-    <string name="youtube_restricted_mode_enabled_title">Slå YouTube \"begrænset tilstand\" til</string>
-    <string name="youtube_restricted_mode_enabled_summary">YouTube har en \"begrænset tilstand\" der skjuler videoer som potientielt er skadelige for børn</string>
+    <string name="auto_queue_toggle">Sæt automatisk i kø</string>
+    <string name="settings_category_player_notification_summary">Konfigurér notifikation om igangværende stream</string>
+    <string name="show_age_restricted_content_summary">Vis indhold, der muligvis er uegnet for børn, fordi det har en aldersgrænse (f.eks. 18+)</string>
+    <string name="youtube_restricted_mode_enabled_title">Slå YouTubes \"Begrænset Tilstand\" til</string>
+    <string name="youtube_restricted_mode_enabled_summary">YouTube tilbyder en \"Begrænset Tilstand\", som skjuler potentielt voksenindhold</string>
     <string name="restricted_video">Denne video er aldersbegrænset.
 \n
-\nSlå \"%1$s\" fra i indstillingerne hvis du vil se den.</string>
+\nSlå \"%1$s\" til i indstillingerne, hvis du vil se den.</string>
     <string name="streams_notification_channel_name">Nye streams</string>
     <string name="streams_notification_channel_description">Notifikationer om nye streams fra abonnementer</string>
-    <string name="recaptcha_cookies_cleared">reCAPTCHA cookies er ryddet</string>
+    <string name="recaptcha_cookies_cleared">reCAPTCHA-cookies blev ryddet</string>
     <string name="delete_playback_states_alert">Slet alle afspilningspositioner\?</string>
     <string name="missing_file">Filen er flyttet eller slettet</string>
     <string name="error_report_notification_title">NewPipe stødte ind i en fejl, tryk for at rapportere</string>
     <string name="error_report_open_issue_button_text">Rapporter på GitHub</string>
     <string name="high_quality_larger">Høj kvalitet (større)</string>
     <string name="enable_queue_limit">Begræns downloadkøen</string>
-    <string name="clear_cookie_summary">Ryd de cookies som NewPipe opbevarer når du løser en reCAPTCHA</string>
-    <string name="notification_colorize_title">Farvelæg notifikationen</string>
-    <string name="settings_category_player_notification_title">Afspillernotifikation</string>
+    <string name="clear_cookie_summary">Ryd de cookies, som NewPipe opbevarer, når du løser en reCAPTCHA</string>
+    <string name="notification_colorize_title">Farvelæg notifikation</string>
+    <string name="settings_category_player_notification_title">Afspiller-notifikation</string>
     <string name="error_report_notification_toast">En fejl opstod, se notifikationen</string>
     <string name="show_description_summary">Slå fra for at skjule videobeskrivelsen og yderligere information</string>
-    <string name="show_meta_info_summary">Slå fra for at gemme metainformationskasser med yderligere information om streammets skaber, streammets indhold eller en søgeforespørgsel</string>
+    <string name="show_meta_info_summary">Slå fra for at skjule metainfo-bokse med yderligere information om streamskaberen, streamindhold eller en søgeforespørgsel</string>
     <plurals name="download_finished_notification">
         <item quantity="one">Download fuldført</item>
         <item quantity="other">%s downloads fuldført</item>
     </plurals>
-    <string name="clear_queue_confirmation_description">Den aktive spilleliste bliver udskiftet</string>
-    <string name="clear_queue_confirmation_summary">Hvis du skifter fra en spiller til en anden, kan din kø blive erstattet</string>
-    <string name="show_meta_info_title">Vis metainformation</string>
+    <string name="clear_queue_confirmation_description">Den aktive afspillerkø bliver udskiftet</string>
+    <string name="clear_queue_confirmation_summary">Ændring fra én afspiller til en anden kan erstatte din kø</string>
+    <string name="show_meta_info_title">Vis metainfo</string>
     <string name="local_search_suggestions">Lokale søgeforslag</string>
-    <string name="remote_search_suggestions">Fjerne søgeforslag</string>
-    <string name="start_main_player_fullscreen_summary">Start ikke videoer i miniafspilleren, men gå direkte til fuldskærmstilstand, hvis automatisk rotering er låst. Du kan stadig se miniafspilleren, hvis du går ud af fuldskærmstilstand</string>
-    <string name="unsupported_url_dialog_message">Kunne ikke genkende addressen. Vil du åbne den i en anden app\?</string>
+    <string name="remote_search_suggestions">Forslag til fjernsøgning</string>
+    <string name="start_main_player_fullscreen_summary">Start ikke videoer i miniafspilleren, men skift direkte til fuldskærmstilstand, hvis automatisk rotation er låst. Du kan stadig få adgang til miniafspilleren ved at forlade fuldskærm</string>
+    <string name="unsupported_url_dialog_message">Kunne ikke genkende URL. Åbn med en anden app?</string>
     <string name="hash_channel_name">Videohashfunktion notifikation</string>
     <string name="hash_channel_description">Notifikationer om videohashfunktioners status</string>
     <string name="error_report_channel_name">Fejlrapport-notifikation</string>
@@ -479,22 +479,22 @@
     <string name="copy_for_github">Kopier en formatteret rapport</string>
     <string name="permission_display_over_apps">Giv tilladelse til at vise over andre apps</string>
     <string name="enable_playback_state_lists_summary">Vis indikatorer for afspilningsposition i lister</string>
-    <string name="watch_history_states_deleted">Afspilningspositioner slettet</string>
-    <string name="clear_cookie_title">Ryd reCAPTCHA cookies</string>
+    <string name="watch_history_states_deleted">Afspilningspositioner blev slettet</string>
+    <string name="clear_cookie_title">Ryd reCAPTCHA-cookies</string>
     <string name="download_already_pending">Der er en afventende download med dette navn</string>
     <string name="start_downloads">Start downloads</string>
     <string name="notification_scale_to_square_image_title">Beskær miniaturebillede til 1:1 format</string>
     <string name="notification_scale_to_square_image_summary">Beskær video-miniaturebillede i notifikationen fra 16:9 til 1:1 format</string>
-    <string name="notification_actions_summary">Rediger hver eneste varselshandling nedenunder ved at trykke på dem. Vælg op til tre af dem som bliver vist i den lille notifikation, via afkrydsningsfelterne til højre</string>
+    <string name="notification_actions_summary">Redigér hver underretningshandling nedenfor ved at trykke på dem. Vælg op til tre af dem, som bliver vist i den lille notifikation via afkrydsningsfelterne til højre.</string>
     <string name="notification_actions_at_most_three">Du kan kun vælge op til tre handlinger som kan vises i den lille notifikation!</string>
-    <string name="notification_action_buffering">Buffer</string>
+    <string name="notification_action_buffering">Buffering</string>
     <string name="notification_colorize_summary">Få Android til at vælge notifikationens farve ud fra den primære farve i miniaturebilledet (virker ikke på alle enheder)</string>
-    <string name="night_theme_title">Nattema</string>
-    <string name="seek_duration_title">Frem- og tilbagesøgningstid</string>
+    <string name="night_theme_title">Nat-tema</string>
+    <string name="seek_duration_title">Søgningsvarighed for spole frem/tilbage</string>
     <string name="restricted_video_no_stream">Denne video er aldersbegrænset.
-\nPga. YouTubes politik om aldersbegrænsede videoer har NewPipe ikke adgang til videoen.</string>
+\nPga. nye YouTube-politikker om aldersbegrænsede videoer har NewPipe ikke adgang til nogen af dens videostreams og kan derfor ikke afspille dem.</string>
     <string name="crash_the_player">Crash afspilleren</string>
-    <string name="clear_queue_confirmation_title">Spørg om bekræftelse før du rydder en kø</string>
+    <string name="clear_queue_confirmation_title">Spørg om bekræftelse, før du rydder en kø</string>
     <string name="seekbar_preview_thumbnail_title">Forhåndsvisning af miniaturebilleder på statuslinjen</string>
     <string name="enqueue_next_stream">Sæt i kø som næste</string>
     <string name="enqueued_next">Er sat som næste i køen</string>
@@ -661,7 +661,7 @@
     <string name="unknown_format">Ukendt format</string>
     <string name="unknown_quality">Ukendt kvalitet</string>
     <string name="detail_heart_img_view_description">Hjertemarkeret af indholdsskaberen</string>
-    <string name="progressive_load_interval_title">Intervalstørrelse for afspilningsindlæsning</string>
+    <string name="progressive_load_interval_title">Størrelse på afspilningsinterval</string>
     <string name="progressive_load_interval_exoplayer_default">ExoPlayer-standard</string>
     <string name="feed_group_dialog_empty_name">Tomt gruppenavn</string>
     <string name="downloads_storage_ask_summary">Du vil blive spurgt, hvor du vil gemme hver enkelt download.
@@ -713,7 +713,7 @@
     <string name="no_audio_streams_available_for_external_players">Ingen lydstreams er tilgængelige for eksterne afspillere</string>
     <string name="select_quality_external_players">Vælg kvalitet til eksterne afspillere</string>
     <string name="sort">Sortér</string>
-    <string name="ignore_hardware_media_buttons_title">Ignorer hardware medie knapper</string>
+    <string name="ignore_hardware_media_buttons_title">Ignorér hardware medie-knap begivenheder</string>
     <string name="ignore_hardware_media_buttons_summary">Brugbart f.eks. hvis du bruger et headset med ødelagte fysiske knapper</string>
     <string name="duplicate_in_playlist">Playlists der er grået ud, indeholder allerede dette objekt.</string>
     <string name="unset_playlist_thumbnail">Inaktiver permanent thumbnail</string>
@@ -721,4 +721,68 @@
     <string name="prefer_original_audio_summary">Brug det originale lydspor uanset sprog</string>
     <string name="prefer_descriptive_audio_title">Foretræk lydbeskrivelser</string>
     <string name="prefer_original_audio_title">Foretræk original lyd</string>
+    <string name="prefer_descriptive_audio_summary">Brug lydbeskrivelser for personer med nedsat syn, hvis tilgængeligt</string>
+    <string name="notification_actions_summary_android13">Redigér hver underretningshandling nedenfor ved at trykke på dem. De første tre handlinger (afspil/sæt på pause, forrige og næste) er indstillet af systemet og kan ikke brugerdefineres.</string>
+    <string name="loading_metadata_title">Indlæser Metadata…</string>
+    <string name="remove_duplicates_title">Fjern duplikater?</string>
+    <string name="image_quality_summary">Vælg kvaliteten af billeder, og om billeder overhovedet skal indlæses, for at reducere data- og hukommelsesforbrug. Ændringer rydder både billedcachen i hukommelsen og på disken — %s</string>
+    <string name="image_quality_medium">Middel kvalitet</string>
+    <string name="image_quality_high">Høj kvalitet</string>
+    <string name="none">Ingen</string>
+    <string name="feed_show_watched">Set helt</string>
+    <string name="no_streams">Ingen streams</string>
+    <string name="feed_show_hide_streams">Vis/skjul streams</string>
+    <string name="brightness">Lysstyrke</string>
+    <string name="volume">Lydstyrke</string>
+    <string name="left_gesture_control_summary">Vælg bevægelse til venstre halvdel af afspillerens skærm</string>
+    <string name="right_gesture_control_summary">Vælg bevægelse til højre halvdel af afspillerens skærm</string>
+    <string name="right_gesture_control_title">Højre bevægelseshandling</string>
+    <string name="no_live_streams">Ingen live streams</string>
+    <string name="play_queue_audio_track">Lyd: %s</string>
+    <string name="audio_track">Lydspor</string>
+    <string name="remove_duplicates">Fjern duplikater</string>
+    <string name="feed_hide_streams_title">Vis følgende streams</string>
+    <string name="feed_fetch_channel_tabs">Hent kanal-faner</string>
+    <string name="feed_fetch_channel_tabs_summary">Faner, der skal hentes, når feedet opdateres. Denne indstilling har ingen effekt, hvis en kanal opdateres i hurtig tilstand.</string>
+    <string name="metadata_thumbnails">Miniaturebilleder</string>
+    <string name="select_audio_track_external_players">Vælg lydspor til eksterne afspillere</string>
+    <string name="unknown_audio_track">Ukendt</string>
+    <string name="feed_show_partially_watched">Delvist set</string>
+    <string name="feed_show_upcoming">Kommende</string>
+    <string name="audio_track_type_original">original</string>
+    <string name="channel_tab_videos">Videoer</string>
+    <string name="channel_tab_tracks">Numre</string>
+    <string name="channel_tab_livestreams">Live</string>
+    <string name="channel_tab_channels">Kanaler</string>
+    <string name="channel_tab_playlists">Playlister</string>
+    <string name="channel_tab_albums">Album</string>
+    <string name="channel_tab_about">Om</string>
+    <string name="show_channel_tabs">Kanal-faner</string>
+    <string name="show_channel_tabs_summary">Hvilke faner vises på kanalsiderne</string>
+    <string name="open_play_queue">Åbn afspilningskø</string>
+    <string name="toggle_fullscreen">Skift til fuld skærm</string>
+    <string name="toggle_screen_orientation">Skift skærmretning</string>
+    <string name="previous_stream">Forrige stream</string>
+    <string name="next_stream">Næste stream</string>
+    <string name="play">Afspil</string>
+    <string name="replay">Afspil igen</string>
+    <string name="duration">Varighed</string>
+    <string name="rewind">Spol tilbage</string>
+    <string name="image_quality_title">Billedkvalitet</string>
+    <string name="image_quality_none">Indlæs ikke billeder</string>
+    <string name="image_quality_low">Lav kvalitet</string>
+    <string name="share_playlist">Del playliste</string>
+    <string name="share_playlist_with_titles_message">Del playliste med detajler såsom playliste navn og videotitler eller som en simpel liste over video-URL\'er</string>
+    <string name="share_playlist_with_titles">Del med Titler</string>
+    <string name="share_playlist_with_list">Del URL-liste</string>
+    <plurals name="replies">
+        <item quantity="one">%s svar</item>
+        <item quantity="other">%s svar</item>
+    </plurals>
+    <string name="show_more">Vis mere</string>
+    <string name="show_less">Vis mindre</string>
+    <string name="progressive_load_interval_summary">Skift intervalstørrelsen for indlæsning af progressivt indhold (i øjeblikket %s). En lavere værdi kan fremskynde den første indlæsning</string>
+    <string name="remove_duplicates_message">Ønsker du at fjerne alle duplikerede streams i denne playliste?</string>
+    <string name="forward">Spol frem</string>
+    <string name="left_gesture_control_title">Venstre bevægelseshandling</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 5260287c1..7fd1d4fed 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -104,7 +104,7 @@
     <string name="popup_remember_size_pos_summary">Letzte Größe und Position des Pop-ups merken</string>
     <string name="show_search_suggestions_title">Suchvorschläge</string>
     <string name="show_search_suggestions_summary">Vorschläge auswählen, die bei der Suche angezeigt werden sollen</string>
-    <string name="clear">Löschen</string>
+    <string name="clear">löschen</string>
     <string name="best_resolution">Beste Auflösung</string>
     <string name="title_activity_about">Über NewPipe</string>
     <string name="tab_licenses">Lizenzen</string>
@@ -425,7 +425,7 @@
     <string name="no_one_watching">Niemand schaut zu</string>
     <plurals name="watching">
         <item quantity="one">%s Zuschauer</item>
-        <item quantity="other">%s Zuschauer</item>
+        <item quantity="other">%s Zuschauende</item>
     </plurals>
     <string name="no_one_listening">Niemand hört zu</string>
     <plurals name="listening">
@@ -541,7 +541,7 @@
     <string name="wifi_only">Nur über WLAN</string>
     <string name="never">Nie</string>
     <string name="notification_actions_at_most_three">Du kannst maximal drei Aktionen auswählen, die in der Kompaktbenachrichtigung angezeigt werden sollen!</string>
-    <string name="notification_actions_summary">Bearbeite jede Benachrichtigungsaktion unten, indem du darauf tippst. Wähle mithilfe der Kontrollkästchen rechts bis zu drei aus, die in der Kompaktbenachrichtigung angezeigt werden sollen</string>
+    <string name="notification_actions_summary">Bearbeite jede Benachrichtigungsaktion unten, indem du auf sie tippst. Wähle mithilfe der Kontrollkästchen rechts bis zu drei aus, die in der Kompaktbenachrichtigung angezeigt werden sollen.</string>
     <string name="unsupported_url_dialog_message">Konnte die angegebene URL nicht erkennen. Mit einer anderen Anwendung öffnen\?</string>
     <string name="notification_action_4_title">Fünfte Aktionstaste</string>
     <string name="notification_action_3_title">Vierte Aktionstaste</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">Wiedergabeliste teilen</string>
     <string name="share_playlist_with_titles_message">Teile die Wiedergabeliste mit Details wie dem Namen der Wiedergabeliste und den Videotiteln oder als einfache Liste von Video-URLs</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s Antwort</item>
+        <item quantity="other">%s Antworten</item>
+    </plurals>
+    <string name="show_more">Mehr zeigen</string>
+    <string name="show_less">Weniger zeigen</string>
+    <string name="notification_actions_summary_android13">Bearbeite jede Benachrichtigungsaktion unten, indem du auf sie tippst. Die ersten drei Aktionen (Abspielen/Pause, Zurück und Weiter) sind vom System vorgegeben und können nicht angepasst werden.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index c2ab656fe..881f91b78 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -482,7 +482,7 @@
     <string name="notification_action_shuffle">Ανάμιξη</string>
     <string name="notification_action_repeat">Επανάληψη</string>
     <string name="notification_actions_at_most_three">Μπορείτε να επιλέξετε το πολύ τρεις ενέργειες για εμφάνιση στη σύντομη ειδοποίηση!</string>
-    <string name="notification_actions_summary">Επεξεργαστείτε κάθε ενέργεια ειδοποίησης παρακάτω πατώντας πάνω της. Επιλέξτε έως και τρεις από αυτές για να εμφανίζονται στη σύντομη ειδοποίηση, χρησιμοποιώντας τα πλαίσια ελέγχου στα δεξιά</string>
+    <string name="notification_actions_summary">Επεξεργαστείτε κάθε ενέργεια ειδοποίησης παρακάτω πατώντας πάνω της. Επιλέξτε έως και τρεις από αυτές για να εμφανίζονται στη σύντομη ειδοποίηση, χρησιμοποιώντας τα πλαίσια ελέγχου στα δεξιά.</string>
     <string name="notification_action_4_title">Κουμπί πέμπτης ενέργειας</string>
     <string name="notification_action_3_title">Κουμπί τέταρτης ενέργειας</string>
     <string name="notification_action_2_title">Κουμπί τρίτης ενέργειας</string>
@@ -595,7 +595,7 @@
     <string name="auto_device_theme_title">Αυτόματο (θέμα συσκευής)</string>
     <string name="night_theme_title">Νυχτερινό θέμα</string>
     <string name="show_channel_details">Εμφάνιση λεπτομερειών καναλιού</string>
-    <string name="disable_media_tunneling_summary">Απενεργοποιήστε το media tunneling, αν εμφανίζεται μαύρη οθόνη ή διακοπτόμενος ήχος κατά την αναπαραγωγή βίντεο</string>
+    <string name="disable_media_tunneling_summary">Απενεργοποιήστε το media tunneling, αν παρατηρείτε μαύρη οθόνη ή διακοπές κατά την αναπαραγωγή βίντεο.</string>
     <string name="disable_media_tunneling_title">Απενεργοποίηση media tunneling</string>
     <string name="metadata_privacy_internal">Εσωτερικό</string>
     <string name="metadata_privacy_private">Ιδιωτικό</string>
@@ -767,7 +767,7 @@
     <string name="metadata_subscribers">Συνδρομητές</string>
     <string name="show_channel_tabs_summary">Ποιες καρτέλες εμφανίζονται στις σελίδες των καναλιών</string>
     <string name="show_channel_tabs">Καρτέλες καναλιών</string>
-    <string name="channel_tab_shorts">Shorts</string>
+    <string name="channel_tab_shorts">Σύντομα</string>
     <string name="feed_fetch_channel_tabs">Λήψη καρτελών καναλιών</string>
     <string name="channel_tab_about">Σχετικά</string>
     <string name="channel_tab_albums">Άλμπουμ</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">Κοινοποίηση λίστας</string>
     <string name="share_playlist_with_titles_message">Μοιραστείτε τη λίστα αναπαραγωγής με λεπτομέρειες όπως το όνομα της λίστας αναπαραγωγής και τους τίτλους βίντεο ή ως μια απλή λίστα διευθύνσεων URL βίντεο</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s απάντηση</item>
+        <item quantity="other">%s απαντήσεις</item>
+    </plurals>
+    <string name="show_more">Εμφάνιση περισσοτέρων</string>
+    <string name="show_less">Εμφάνιση λιγότερων</string>
+    <string name="notification_actions_summary_android13">Επεξεργαστείτε κάθε ενέργεια ειδοποίησης παρακάτω πατώντας σε αυτήν. Οι τρεις πρώτες ενέργειες (αναπαραγωγή/παύση, προηγούμενηο και επόμενο) ορίζονται από το σύστημα και δεν μπορούν να τροποποιηθούν.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 44e3348ca..90f5fd2ec 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -610,4 +610,5 @@
     <string name="related_items_tab_description">Rilatajn erojn</string>
     <string name="recaptcha_solve">Solvi</string>
     <string name="msg_failed_to_copy">Malsukcesis kopii al la tondujo</string>
+    <string name="downloads_storage_ask_summary_no_saf_notice">Oni petos al vi kien salvi ĉiujn elŝutojn</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 499610d9b..2da61b8d5 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -47,7 +47,7 @@
     <string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string>
     <string name="content">Contenido</string>
     <string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string>
-    <string name="main_bg_subtitle">Pulsa la lupa para empezar.</string>
+    <string name="main_bg_subtitle">Toca la lupa para empezar.</string>
     <string name="duration_live">En directo</string>
     <string name="downloads">Descargas</string>
     <string name="downloads_title">Descargas</string>
@@ -558,7 +558,7 @@
     <string name="notification_action_buffering">Almacenar en memoria (búfer)</string>
     <string name="notification_action_repeat">Repetir</string>
     <string name="notification_actions_at_most_three">¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta!</string>
-    <string name="notification_actions_summary">Edite cada una de las acciones de notificación que aparecen a continuación pulsando sobre ellas. Seleccione hasta tres de ellas para que se muestren en la notificación compacta utilizando las casillas de verificación de la derecha</string>
+    <string name="notification_actions_summary">Edite cada acción de notificación pulsando sobre ella. Seleccione hasta tres de ellas para que se muestren en la notificación compacta utilizando las casillas de verificación de la derecha.</string>
     <string name="notification_action_4_title">Botón de quinta acción</string>
     <string name="notification_action_3_title">Botón de cuarta acción</string>
     <string name="notification_action_2_title">Botón de tercera acción</string>
@@ -822,4 +822,12 @@
     <string name="share_playlist">Compartir la lista de reproducción</string>
     <string name="share_playlist_with_titles_message">Compartir las listas de reproducción con los detalles como el nombre de la lista y los títulos de los vídeos o como una simple lista de una dirección URL con los vídeos</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s respuesta</item>
+        <item quantity="many">%s respuestas</item>
+        <item quantity="other">%s respuestas</item>
+    </plurals>
+    <string name="show_more">Ver más</string>
+    <string name="show_less">Mostrar menos</string>
+    <string name="notification_actions_summary_android13">Edite cada acción de notificación pulsando sobre ella. Las tres primeras acciones (reproducir/pausa, anterior y siguiente) las establece el sistema y no se pueden personalizar.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index fd84e5d45..1a9c5daac 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -411,7 +411,7 @@
     <string name="notification_action_shuffle">Aja segi</string>
     <string name="notification_action_repeat">Korda</string>
     <string name="notification_actions_at_most_three">Sa saad valida kuni kolm tegevust, mida kuvatakse lühiteavituses!</string>
-    <string name="notification_actions_summary">Muuda iga teavituse tegevusi sellel toksates. Vali märkekastides paremal kuni kolm teavitust, mida kuvada lühiteates</string>
+    <string name="notification_actions_summary">Muuda iga teavituse tegevusi sellel toksates. Vali märkekastides paremal kuni kolm teavitust, mida kuvada lühiteates.</string>
     <string name="notification_action_4_title">Viies tegevusnupp</string>
     <string name="notification_action_3_title">Neljas tegevusnupp</string>
     <string name="notification_action_2_title">Kolmas tegevusnupp</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">Jaga esitusloendit</string>
     <string name="share_playlist_with_titles_message">Jaga esitusloendit kas väga detailse teabega palade kohta või lihtsa url\'ide loendina</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="show_more">Näita veel</string>
+    <plurals name="replies">
+        <item quantity="one">%s vastus</item>
+        <item quantity="other">%s vastust</item>
+    </plurals>
+    <string name="show_less">Näita vähem</string>
+    <string name="notification_actions_summary_android13">Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 958c82d07..a8ee1093e 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -789,7 +789,14 @@
     <string name="channel_tab_channels">Kanavat</string>
     <string name="you_successfully_subscribed">Olet nyt tilannut tämän kanavan</string>
     <string name="previous_stream">Edellinen stream</string>
-    <string name="channel_tab_livestreams"/>
+    <string name="channel_tab_livestreams">Live</string>
     <string name="remove_duplicates_message">Haluatko poistaa kaikki ylimääräiset identtiset suoratoistot tästä soittolistasta\?</string>
     <string name="streams_not_yet_supported_removed">Suoratoistot, joita lataaja ei vielä tue, ei näytetä</string>
+    <plurals name="replies">
+        <item quantity="one">%s vastaus</item>
+        <item quantity="other">%s vastausta</item>
+    </plurals>
+    <string name="show_more">Näytä lisää</string>
+    <string name="show_less">Näytä vähemmän</string>
+    <string name="show_channel_tabs">Kanavan välilehdet</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 738f9f58f..81ab11b88 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -557,7 +557,7 @@
     <string name="notification_action_shuffle">Lire aléatoirement</string>
     <string name="notification_action_repeat">Répéter</string>
     <string name="notification_actions_at_most_three">Vous pouvez sélectionner au maximum trois actions à faire figurer dans la notification compacte !</string>
-    <string name="notification_actions_summary">Modifiez chaque action de notification ci-dessous en appuyant dessus. Sélectionnez jusqu’à trois d’entre elles pour les faire apparaitre dans la notification compacte en utilisant les cases à cocher à droite</string>
+    <string name="notification_actions_summary">Modifiez chaque action de notification ci-dessous en appuyant dessus. Sélectionnez jusqu’à trois d’entre elles pour les faire apparaître dans la notification compacte en utilisant les cases à cocher à droite.</string>
     <string name="notification_action_4_title">Cinquième bouton d’action</string>
     <string name="notification_action_3_title">Quatrième bouton d’action</string>
     <string name="notification_action_2_title">Troisième bouton d’action</string>
@@ -821,4 +821,12 @@
     <string name="metadata_uploader_avatars">Avatars du téléverseur</string>
     <string name="image_quality_summary">Sélectionnez la qualité des images et si les images doivent être chargées, pour réduire l\'utilisation de la mémoire et de données. Les modifications vident à la fois le cache des images en mémoire et sur le disque — %s</string>
     <string name="play">Lire</string>
+    <plurals name="replies">
+        <item quantity="one">%s réponse</item>
+        <item quantity="many">%s réponses</item>
+        <item quantity="other">%s réponses</item>
+    </plurals>
+    <string name="notification_actions_summary_android13">Modifiez chaque action de notification ci-dessous en appuyant dessus. Les trois premières actions (lire/pause, précédent, suivant) sont définies par le système et ne peuvent pas être personnalisées.</string>
+    <string name="show_more">Afficher plus</string>
+    <string name="show_less">Afficher moins</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index 729db00b6..5357e38fd 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -564,7 +564,7 @@
     <string name="notification_action_shuffle">ערבוב</string>
     <string name="notification_action_repeat">חזרה</string>
     <string name="notification_actions_at_most_three">ניתן לבחור עד שלוש פעולות בלבד שתופענה בהתראות המצומצמות!</string>
-    <string name="notification_actions_summary">ניתן לערוך כל התראה להלן בלחיצה עליה. מותר לבחור עד שלוש מהן שתופענה בהתראה המצומצמת באמצעות תיבות הסימן שמשמאל</string>
+    <string name="notification_actions_summary">ניתן לערוך כל התראה להלן בלחיצה עליה. מותר לבחור עד שלוש מהן שתופענה בהתראה המצומצמת באמצעות תיבות הסימן שמשמאל.</string>
     <string name="notification_action_4_title">כפתור פעולה חמישי</string>
     <string name="notification_action_3_title">כפתור פעולה רביעי</string>
     <string name="notification_action_2_title">כפתור פעולה שלישי</string>
@@ -832,4 +832,12 @@
     <string name="share_playlist">שיתוף רשימת נגינה</string>
     <string name="share_playlist_with_titles_message">שיתוף רשימת נגינה עם פרטים כגון שם רשימת נגינה וכותרות סרטונים או כרשימה פשוטה של כתובות סרטונים</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="show_more">להציג עוד</string>
+    <string name="show_less">להציג פחות</string>
+    <plurals name="replies">
+        <item quantity="one">תשובה %s</item>
+        <item quantity="two">%s תשובות</item>
+        <item quantity="other">%s תשובות</item>
+    </plurals>
+    <string name="notification_actions_summary_android13">אפשר לערוך כל פעולה בהתראה להלן על ידי נגיעה בה. שלוש הפעולות הראשונות (נגינה/השהיה, הקודם והבא) מוגדרות על ידי המערכת ונעולות לעריכה.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 9111f9444..d155fab95 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -200,7 +200,7 @@
     <string name="export_data_summary">इतिहास, सब्सक्रिप्शन, प्लेलिस्ट और सेटिंग निर्यात करें</string>
     <string name="external_player_unsupported_link_type">बाहरी प्लेयर्स इन प्रकार के लिंक सपोर्ट नहीं करते</string>
     <string name="video_streams_empty">कोई वीडियो स्ट्रीम नहीं मिला</string>
-    <string name="audio_streams_empty">कोई ऑडियो स्ट्रीम नहीं मिली</string>
+    <string name="audio_streams_empty">कोई ऑडियो स्ट्रीम नहीं मिला</string>
     <string name="detail_drag_description">फिर से क्रम देने के लिए खींचें</string>
     <string name="create">बनाइये</string>
     <string name="dismiss">ख़ारिज करें</string>
@@ -471,7 +471,7 @@
     <string name="notification_action_shuffle">शफल करें</string>
     <string name="notification_action_repeat">दोहराएं</string>
     <string name="notification_actions_at_most_three">आप कंपैकट नोटीफिकेशन में दिखाए जाने वाले विकल्प में से अधिकतम 3 को चुन सकते है !</string>
-    <string name="notification_actions_summary">नीचे दी गई प्रत्येक नोटीफिकेशन क्रिया को उस पर टैप करके संपादित करें। दाईं ओर चेकबॉक्स का उपयोग करके उनमें से अधिकतम तीन का चयन करें जिन्हें कंपैकट नोटीफिकेशन में दिखाया जाना है</string>
+    <string name="notification_actions_summary">नीचे दी गई प्रत्येक नोटीफिकेशन क्रिया को उस पर टैप करके संपादित करें। दाईं ओर चेकबॉक्स का उपयोग करके उनमें से अधिकतम तीन का चयन करें जिन्हें कंपैकट नोटीफिकेशन में दिखाया जाना है।</string>
     <string name="notification_action_4_title">पांचवा एक्शन बटन</string>
     <string name="notification_action_3_title">चतुर्थी एक्शन बटन</string>
     <string name="notification_action_2_title">तृतीय एक्शन बटन</string>
@@ -628,7 +628,7 @@
     <string name="semitone">सैमीटोन</string>
     <plurals name="download_finished_notification">
         <item quantity="one">डाउनलोड संपूर्ण</item>
-        <item quantity="other">%s डाउनलोडस संपूर्ण</item>
+        <item quantity="other">%s डाउनलोड संपूर्ण</item>
     </plurals>
     <plurals name="days">
         <item quantity="one">%d दिन</item>
@@ -806,4 +806,11 @@
     <string name="share_playlist">प्लेलिस्ट साझा करें</string>
     <string name="share_playlist_with_titles_message">प्लेलिस्ट को प्लेलिस्ट नाम और वीडियो शीर्षक जैसे विवरण के साथ या वीडियो यूआरएल की एक सरल सूची के रूप में साझा करें</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s जवाब</item>
+        <item quantity="other">%s जवाब</item>
+    </plurals>
+    <string name="show_more">और दिखाओ</string>
+    <string name="notification_actions_summary_android13">नीचे दी गई प्रत्येक अधिसूचना कार्रवाई पर टैप करके उसे संपादित करें। पहली तीन क्रियाएँ (चलाएँ/रोकें, पिछली और अगली) सिस्टम द्वारा निर्धारित की जाती हैं और इन्हें अनुकूलित नहीं किया जा सकता है।</string>
+    <string name="show_less">कम दिखाएं</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 13535994a..330ae2d5c 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -15,7 +15,7 @@
     <string name="share_dialog_title">Dijeli s</string>
     <string name="use_external_video_player_title">Koristi vanjski video player</string>
     <string name="use_external_video_player_summary">Uklanja audiosnimku pri nekim rezolucijama</string>
-    <string name="use_external_audio_player_title">Koristi vanjski audio player</string>
+    <string name="use_external_audio_player_title">Koristi eksterni audio player</string>
     <string name="subscribe_button_title">Pretplati se</string>
     <string name="subscribed_button_title">Pretplaćeno</string>
     <string name="channel_unsubscribed">Pretplata na kanal otkazana</string>
@@ -36,7 +36,7 @@
     <string name="show_higher_resolutions_title">Prikaži veće rezolucije</string>
     <string name="show_higher_resolutions_summary">Samo neki uređaji podržavaju reprodukciju 2K/4K videa</string>
     <string name="play_with_kodi_title">Reproduciraj s Kodijem</string>
-    <string name="kore_not_found">Instalirati nedostajući Kode program\?</string>
+    <string name="kore_not_found">Instalirati nedostajuću Kore aplikaciju?</string>
     <string name="show_play_with_kodi_title">Prikaži opciju „Reproduciraj pomoću Kodija”</string>
     <string name="show_play_with_kodi_summary">Prikaži opciju za reproduciranje videozapisa putem Kodija</string>
     <string name="play_audio">Audiosnimka</string>
@@ -56,7 +56,7 @@
     <string name="enable_watch_history_summary">Prati gledana videa</string>
     <string name="resume_on_audio_focus_gain_title">Nastavi reprodukciju</string>
     <string name="resume_on_audio_focus_gain_summary">Nastavi reproducirati nakon prekidanja (npr. telefonski pozivi)</string>
-    <string name="download_dialog_title">Preuzmi</string>
+    <string name="download_dialog_title">Preuzimanje</string>
     <string name="show_next_and_similar_title">Prikaži videa „Sljedeći” i „Slični”</string>
     <string name="unsupported_url">URL nije podržan</string>
     <string name="content_language_title">Zadani jezik sadržaja</string>
@@ -69,7 +69,7 @@
     <string name="duration_live">Uživo</string>
     <string name="downloads">Preuzimanja</string>
     <string name="downloads_title">Preuzimanja</string>
-    <string name="error_report_title">Prijavi grešku</string>
+    <string name="error_report_title">Izvještaj o grešci</string>
     <string name="all">Sve</string>
     <string name="disabled">Isključeno</string>
     <string name="clear">Očisti</string>
@@ -80,14 +80,14 @@
     <string name="parsing_error">Nije bilo moguće obraditi stranicu</string>
     <string name="content_not_available">Sadržaj nije dostupan</string>
     <string name="could_not_setup_download_menu">Nije bilo moguće postaviti izbornik za preuzimanje</string>
-    <string name="app_ui_crash">Program/korisničko sučelje su preknuli raditi</string>
+    <string name="app_ui_crash">Aplikacija/korisničko sučelje su preknuli raditi</string>
     <string name="sorry_string">Oprosti, ovo se nije trebalo dogoditi.</string>
-    <string name="error_report_button_text">Prijavi pogrešku putem e-maila</string>
+    <string name="error_report_button_text">Prijavi putem e-maila</string>
     <string name="error_snackbar_message">Žao nam je, došlo je do neke greške.</string>
     <string name="error_snackbar_action">Prijavi</string>
     <string name="what_device_headline">Informacije:</string>
     <string name="what_happened_headline">Što se dogodilo:</string>
-    <string name="info_labels">Što:\\nZahtjev:\\nJezik sadržaja:\\nZemlja sadržaja:\\nJezik programa:\\nUsluga:\\nGMT vrijeme:\\nPaket:\\nVerzija:\\nVerzija OS-a:</string>
+    <string name="info_labels">Što:\\nZahtjev:\\nJezik sadržaja:\\nZemlja sadržaja:\\nJezik aplikacije:\\nUsluga:\\nGMT vrijeme:\\nPaket:\\nVerzija:\\nVerzija OS-a:</string>
     <string name="your_comment">Tvoj komentar (na engleskom):</string>
     <string name="error_details_headline">Detalji:</string>
     <string name="detail_thumbnail_view_description">Pokreni video, trajanje:</string>
@@ -128,7 +128,7 @@
     <string name="copyright" formatted="true">© %1$s od %2$s pod %3$s</string>
     <string name="tab_about">O aplikaciji i ČPP</string>
     <string name="tab_licenses">Licence</string>
-    <string name="app_description">Slobodan i mali YouTube program za Android.</string>
+    <string name="app_description">Slobodan i mali streaming na Android uređaju.</string>
     <string name="view_on_github">Pogledaj na GitHubu</string>
     <string name="app_license_title">NewPipe licenca</string>
     <string name="contribution_encouragement">Ako imate ideja za prijevod, promjene u dizajnu, čišćenje koda ili neke veće promjene u kodu, pomoć je uvijek dobro došla. Što više radimo, to bolji postajemo!</string>
@@ -181,8 +181,8 @@
     <string name="new_and_hot">Novi i popularni</string>
     <string name="play_queue_remove">Ukloni</string>
     <string name="play_queue_stream_detail">Detalji</string>
-    <string name="play_queue_audio_settings">Postavke za audiosnimke</string>
-    <string name="hold_to_append">Drži pritisnuto za dodavanje u popis izvođenja</string>
+    <string name="play_queue_audio_settings">Postavke za audio snimke</string>
+    <string name="hold_to_append">Drži pritisnuto za dodavanje u popis</string>
     <string name="unknown_content">[Nepoznato]</string>
     <string name="donation_title">Doniraj</string>
     <string name="website_title">Web-stranica</string>
@@ -212,7 +212,7 @@
     <string name="resize_fill">Ispuni</string>
     <string name="resize_zoom">Zumiraj</string>
     <string name="caption_auto_generated">Automatski generirani</string>
-    <string name="enable_leak_canary_summary">Praćenje curenja memorije može uzrokovati greške u radu programa prilikom odlaganje gomile</string>
+    <string name="enable_leak_canary_summary">Praćenje curenja memorije može uzrokovati greške u radu aplikacije prilikom odlaganje gomile</string>
     <string name="enable_disposed_exceptions_title">Izvijesti o krajevima životnog ciklusa</string>
     <string name="show_info">Prikaži informacije</string>
     <string name="tab_bookmarks">Zabilježene playliste</string>
@@ -302,12 +302,12 @@
     <string name="file_deleted">Datoteka obrisana</string>
     <string name="app_update_notification_channel_description">Obavijest za nove NewPipe verzije</string>
     <string name="clear_search_history_summary">Briše povijest ključnih riječi pretraživanja</string>
-    <string name="download_to_sdcard_error_title">Vanjska pohrana nije dostupna</string>
+    <string name="download_to_sdcard_error_title">Eksterna memorija nije dostupna</string>
     <string name="updates_setting_title">Aktualiziranja</string>
-    <string name="updates_setting_description">Prikaži obavijest i zatraži aktualiziranje programa kad je dostupna nova verzija</string>
+    <string name="updates_setting_description">Prikaži obavijest za aktualiziranje aplikacije kad je dostupna nova verzija</string>
     <string name="list">Popis</string>
-    <string name="grid">Popločeno</string>
-    <string name="app_update_available_notification_title">Dostupna je nova verzija za NewPipe!</string>
+    <string name="grid">Mreža</string>
+    <string name="app_update_available_notification_title">Dostupna je nova NewPipe verzija!</string>
     <string name="download_failed">Preuzimanje nije uspjelo</string>
     <string name="show_error">Prikaži pogrešku</string>
     <string name="metadata_cache_wipe_summary">Izbriši sve podatke web-stranica iz predmemorije</string>
@@ -316,9 +316,9 @@
     <string name="auto_queue_summary">Nastavi završavati (ne ponavljajući) popis reprodukcija dodavanjem povezanog streama</string>
     <string name="default_content_country_title">Zadana zemlja sadržaja</string>
     <string name="settings_category_debug_title">Otkrivanje grešaka</string>
-    <string name="app_update_notification_channel_name">Obavijest o novoj verziji programa</string>
-    <string name="download_to_sdcard_error_message">Preuzimanje na vanjsku SD karticu nije moguće. Ponovo postaviti lokaciju mape za preuzimanje\?</string>
-    <string name="external_player_unsupported_link_type">Vanjski playeri ne podržavaju ove vrste poveznica</string>
+    <string name="app_update_notification_channel_name">Obavijest o novoj verziji aplikacije</string>
+    <string name="download_to_sdcard_error_message">Preuzimanje na eksternu SD karticu nije moguće. Ponovo postaviti lokaciju mape za preuzimanje?</string>
+    <string name="external_player_unsupported_link_type">Eksterni playeri ne podržavaju ove vrste poveznica</string>
     <string name="video_streams_empty">Nije pronađen nijedan videozapis</string>
     <string name="audio_streams_empty">Nije pronađena nijedna audiosnimka</string>
     <string name="invalid_source">Nema takve datoteke/izvora sadržaja</string>
@@ -328,13 +328,13 @@
     <string name="restore_defaults">Obnovi standardne vrijednosti</string>
     <string name="restore_defaults_confirmation">Želiš li obnoviti standardne vrijednosti\?</string>
     <string name="subscribers_count_not_available">Broj pretplatnika nije dostupan</string>
-    <string name="donation_encouragement">NewPipe razvijaju volonteri koji provode vrijeme kako bi doprinijeli najboljem iskustvu. Doprinesi programerima kako bi poboljšali NewPipe dok uživaju u šalici kave.</string>
+    <string name="donation_encouragement">NewPipe razvijaju volonteri koji provode vrijeme kako bi doprinijeli najboljem korisničkom iskustvu. Doprinesi programerima kako bi poboljšali NewPipe dok uživaju u šalici kave.</string>
     <string name="main_page_content_summary">Koje su kartice prikazane na glavnoj stranici</string>
     <string name="conferences">Konferencije</string>
     <string name="preferred_open_action_settings_title">Željena radnja za otvaranje</string>
     <string name="preferred_open_action_settings_summary">Zadana radnja pri otvaranju sadržaja — %s</string>
     <string name="caption_setting_title">Titlovi</string>
-    <string name="caption_setting_description">Promijeni veličinu podnaslova i pozadinske stilove playera. Zahtijeva ponovno pokretanje programa</string>
+    <string name="caption_setting_description">Promijeni veličinu titlova i stilove pozadine playera. Zahtijeva ponovno pokretanje aplikacije</string>
     <string name="enable_disposed_exceptions_summary">Prisilno izvijesti o neisporučivim Rx iznimaka izvan fragmenta ili životnog ciklusa aktivnosti nakon odlaganja</string>
     <string name="import_soundcloud_instructions">Uvezi SoundCloud profil upisom URL-a ili svog ID-a:
 \n
@@ -345,8 +345,8 @@
     <string name="playback_tempo">Brzina</string>
     <string name="playback_pitch">Visina tona</string>
     <string name="unhook_checkbox">Odspoji (može prouzročiti izobličenje)</string>
-    <string name="minimize_on_exit_title">Smanji prilikom mijenjanje programa</string>
-    <string name="minimize_on_exit_summary">Radnja prilikom prebacivanja na drugi program iz glavnog video playera – %s</string>
+    <string name="minimize_on_exit_title">Smanji prilikom mijenjanje aplikacije</string>
+    <string name="minimize_on_exit_summary">Radnja prilikom prebacivanja na drugu aplikaciju iz glavnog video playera – %s</string>
     <string name="minimize_on_exit_background_description">Smanji na pozadinski player</string>
     <string name="minimize_on_exit_popup_description">Smanji na skočni player</string>
     <string name="list_view_mode">Način prikaza kao popis</string>
@@ -356,7 +356,7 @@
     <string name="paused">pauzirano</string>
     <string name="queued">stavljeno u popis izvođenja</string>
     <string name="post_processing">naknadna obrada</string>
-    <string name="enqueue">Popis izvođenja</string>
+    <string name="enqueue">Dodaj u popis</string>
     <string name="permission_denied">Sustav je odbio radnju</string>
     <string name="generate_unique_name">Generiraj jedinstveni naziv</string>
     <string name="overwrite">Prepiši</string>
@@ -384,8 +384,8 @@
     <string name="error_unable_to_load_comments">Nije bilo moguće učitati komentare</string>
     <string name="close">Zatvori</string>
     <string name="app_license">NewPipe je copyleft libre softver: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila zaklada Free Software Foundation, pod verzijom 3 licence, ili (po vlastitom izboru) bilo koje kasnije verzije.</string>
-    <string name="privacy_policy_encouragement">Projekt NewPipe ozbiljno shvaća tvou privatnost. Stoga program ne prikuplja nikakve podatke bez tvog pristanka.
-\nNewPipe pravila o privatnosti detaljno objašnjavaju koji se podaci šalju i spremaju kad šalješ izvještaje o prekidu rada programa.</string>
+    <string name="privacy_policy_encouragement">Projekt NewPipe ozbiljno shvaća temu o privatnosti. Stoga aplikacija ne prikuplja podatke bez tvog pristanka.
+\nNewPipe pravila o privatnosti detaljno objašnjavaju koji se podaci šalju i spremaju kad šalješ izvještaje o prekidu rada aplikacije.</string>
     <string name="start_accept_privacy_policy">Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), ovime upozoravamo na NewPipe politiku privatnosti. Pažljivo je pročitaj.
 \nZa slanje izvješća o pogreškama moraš prihvatiti politiku privatnosti.</string>
     <string name="enable_playback_resume_title">Nastavi reprodukciju</string>
@@ -406,7 +406,7 @@
     <string name="delete_playback_states_alert">Izbrisati sve pozicije reprodukcije\?</string>
     <string name="no_one_watching">Nitko ne gleda</string>
     <string name="no_one_listening">Nitko ne sluša</string>
-    <string name="localization_changes_requires_app_restart">Jezik će se promijeniti nakon ponovnog pokretanja programa</string>
+    <string name="localization_changes_requires_app_restart">Jezik će se promijeniti nakon ponovnog pokretanja aplikcije</string>
     <string name="default_kiosk_page_summary">Standardni kiosk</string>
     <string name="peertube_instance_add_https_only">Podržani su samo HTTP URL-ovi</string>
     <string name="local">Lokalni</string>
@@ -414,8 +414,8 @@
     <string name="playlist_no_uploader">Automatski generirano (prenositelj nedefiniran)</string>
     <string name="clear_download_history">Očisti povijest preuzimanja</string>
     <string name="delete_downloaded_files">Izbriši preuzete datoteke</string>
-    <string name="permission_display_over_apps">Dopusti prikaz iznad drugih programa</string>
-    <string name="app_language_title">Jezik programa</string>
+    <string name="permission_display_over_apps">Dopusti prikaz preko drugih aplikacija</string>
+    <string name="app_language_title">Jezik aplikacije</string>
     <string name="systems_language">Zadani sustav</string>
     <string name="videos_string">Videa</string>
     <string name="mute">Isključi zvuk</string>
@@ -424,8 +424,8 @@
     <string name="feed_group_dialog_delete_message">Želiš li izbrisati ovu grupu\?</string>
     <string name="feed_create_new_group_button_title">Nova</string>
     <string name="feed_update_threshold_option_always_update">Uvijek aktualiziraj</string>
-    <string name="feed_use_dedicated_fetch_method_enable_button">Omogući brz način</string>
-    <string name="feed_use_dedicated_fetch_method_disable_button">Onemogući brz način</string>
+    <string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi način</string>
+    <string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi način</string>
     <string name="error_insufficient_storage">Memorija uređaja je popunjena</string>
     <string name="most_liked">Najomiljeniji</string>
     <string name="subtitle_activity_recaptcha">Pritisni „Gotovo” kad je riješeno</string>
@@ -433,13 +433,13 @@
     <string name="infinite_videos">∞ videa</string>
     <string name="more_than_100_videos">Više od 100 videa</string>
     <string name="error_report_open_issue_button_text">Prijavi grešku na GitHub-u</string>
-    <string name="artists">Umjetnici</string>
+    <string name="artists">Izvođači</string>
     <string name="albums">Albumi</string>
     <string name="songs">Pjesme</string>
     <string name="channel_created_by">Stvoren od %s</string>
     <string name="never">Nikada</string>
     <string name="enable_queue_limit">Ograniči popis preuzimanja</string>
-    <string name="downloads_storage_use_saf_title">Koristi birač mapa sustava (SAF)</string>
+    <string name="downloads_storage_use_saf_title">Koristi sustavksi birač mapa (SAF)</string>
     <string name="remove_watched">Ukloni pregledano</string>
     <string name="remove_watched_popup_title">Ukloni pogledana videa\?</string>
     <plurals name="seconds">
@@ -471,12 +471,12 @@
     <string name="notification_action_1_title">Gumb druge radnje</string>
     <string name="notification_action_0_title">Gumb prve radnje</string>
     <string name="search_showing_result_for">Prikazuju se rezultati za: %s</string>
-    <string name="unsupported_url_dialog_message">Nije bilo moguće prepoznati URL. Želiš li otvoriti s drugim programom\?</string>
+    <string name="unsupported_url_dialog_message">Nije bilo moguće prepoznati URL. Otvoriti s jednom drugom aplikacijom?</string>
     <string name="notification_scale_to_square_image_title">Odreži sličicu na omjer 1:1</string>
     <string name="notification_action_buffering">Učitavanje u predmemoriju</string>
     <string name="enable_queue_limit_desc">Istovremeno se pokreće jedno preuzimanje</string>
     <string name="enqueued">Dodano u popis izvođenja</string>
-    <string name="enqueue_stream">Dodaj u popis izvođenja</string>
+    <string name="enqueue_stream">Dodaj u popis</string>
     <string name="title_activity_play_queue">Reproduciraj popis izvođenja</string>
     <string name="auto_queue_toggle">Automatski popis izvođenja</string>
     <string name="clear_queue_confirmation_description">Popis izvođenja aktivnog playera će se zamijeniti</string>
@@ -509,7 +509,7 @@
     <string name="remove_watched_popup_yes_and_partially_watched_videos">Da, i djelomično pogledana videa</string>
     <string name="choose_instance_prompt">Odaberi jednu instancu</string>
     <string name="downloads_storage_ask_summary">Aplikacija će te pitati kamo spremati preuzimanja.
-\nOmogući birač mapa sustava (SAF), ako želiš preuzimati na vanjsku SD karticu</string>
+\nUključi sustavksi birač mapa (SAF) ako želiš preuzeti na eksternu SD karticu</string>
     <string name="error_download_resource_gone">Nije moguće obnoviti ovo preuzimanje</string>
     <string name="error_progress_lost">Napredak je izgubljen, jer je datoteka izbrisana</string>
     <string name="error_postprocessing_stopped">NewPipe se zatvorio tijekom rada s datotekom</string>
@@ -550,7 +550,7 @@
     <string name="notification_action_shuffle">Promiješaj</string>
     <string name="notification_action_repeat">Ponovi</string>
     <string name="error_report_open_github_notice">Provjeri je li problem već postoji. Prijavljivanje istog već prijavljenog problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje greške.</string>
-    <string name="notification_actions_summary">Za uređivanje radnji u obavijestima, dodirni ih. Označi do tri radnje za prikaz u kompaktnoj obavijesti koristeći oznake na desnoj strani</string>
+    <string name="notification_actions_summary">Uredi radnje obavijesti dodirom. Označi do tri radnje za prikaz u kompaktnoj obavijesti koristeći potvrdna polja na desnoj strani.</string>
     <string name="new_seek_duration_toast">Zbog ograničenja ExoPlayera, trajanje premotavanja postavljeno je na %d s</string>
     <string name="notification_colorize_summary">Neka Android prilagodi boju obavijesti prema glavnoj boji sličice (ovo nije dostupno na svim uređajima)</string>
     <string name="notification_colorize_title">Oboji obavijest</string>
@@ -560,7 +560,7 @@
     <string name="show_thumbnail_summary">Koristi sličicu za pozadinu zaključanog ekrana i za obavijesti</string>
     <string name="show_thumbnail_title">Prikaži sličicu</string>
     <string name="show_original_time_ago_title">Prikaži izvorno vrijeme stavki</string>
-    <string name="downloads_storage_use_saf_summary">„Storage Access Framework” omogućuje preuzimanje na SD karticu</string>
+    <string name="downloads_storage_use_saf_summary">„Storage Access Framework” dozvoljava preuzimanje na eksternu SD karticu</string>
     <string name="show_original_time_ago_summary">Izvorni tekstovi usluga bit će vidljivi u stavkama prijenosa</string>
     <string name="feed_use_dedicated_fetch_method_summary">Dostupno je u nekim uslugama. Obično je puno brže, ali može dohvatiti ograničenu količinu stavki i često nepotpune podatke (npr. bez trajanja, vrste stavke, bez stanja uživo)</string>
     <string name="feed_use_dedicated_fetch_method_help_text">Misliš da je učitavanje feeda presporo\? Ako da, pokušaj omogućiti brzo učitavanje (možeš ga promijeniti u postavkama ili pritiskom na donji gumb).
@@ -581,7 +581,7 @@
     <string name="show_meta_info_summary">Isključi za skrivanje polja metapodataka s dodatnim podacima o autoru streama, sadržaju streama ili zahtjevu za pretraživanje</string>
     <string name="show_meta_info_title">Prikaži metapodatke</string>
     <string name="related_items_tab_description">Povezane stavke</string>
-    <string name="no_app_to_open_intent">Nijedan program na tvom uređaju ovo ne može otvoriti</string>
+    <string name="no_app_to_open_intent">Nijedna aplikacija na tvom uređaju ovo ne može otvoriti</string>
     <string name="chapters">Poglavlja</string>
     <string name="description_tab_description">Opis</string>
     <string name="comments_tab_description">Komentari</string>
@@ -601,12 +601,12 @@
     <string name="youtube_music_premium_content">Ovaj je video dostupan samo za „YouTube Music Premium” članove, stoga ga NewPipe ne može emitirati ili preuzeti.</string>
     <string name="private_content">Ovaj sadržaj je privatan, stoga ga NewPipe ne može emitirati ili preuzeti.</string>
     <string name="georestricted_content">Ovaj sadržaj nije dostupan u tvojoj zemlji.</string>
-    <string name="crash_the_app">Prekini program</string>
+    <string name="crash_the_app">Prekini aplikaciju</string>
     <string name="recaptcha_solve">Riješi</string>
-    <string name="night_theme_title">Noćna tema</string>
+    <string name="night_theme_title">Tamna tema</string>
     <string name="show_channel_details">Prikaži detalje kanala</string>
     <string name="disable_media_tunneling_summary">Isključi tuneliranje medija ako doživiš crni ekran ili isprekidanu reprodukciju videa.</string>
-    <string name="disable_media_tunneling_title">Iskljuci medija tuneling</string>
+    <string name="disable_media_tunneling_title">Isključi tuneliranje medija</string>
     <string name="off">Isklj.</string>
     <string name="on">Uklj.</string>
     <string name="tablet_mode_title">Način rada na tabletu</string>
@@ -615,14 +615,14 @@
     <string name="metadata_privacy_private">Privatno</string>
     <string name="metadata_privacy_unlisted">Nenavedeno</string>
     <string name="metadata_privacy_public">Javno</string>
-    <string name="metadata_host">Poslužitelj</string>
+    <string name="metadata_host">Računalo</string>
     <string name="metadata_support">Podrška</string>
     <string name="metadata_language">Jezik</string>
     <string name="metadata_age_limit">Dobna granica</string>
     <string name="metadata_licence">Licenca</string>
     <string name="metadata_tags">Oznake</string>
     <string name="metadata_category">Kategorija</string>
-    <string name="description_select_disable">Onemogući biranje teksta u opisu</string>
+    <string name="description_select_disable">Isključi biranje teksta u opisu</string>
     <string name="description_select_enable">Omogući biranje teksta u opisu</string>
     <string name="account_terminated">Račun ukinut</string>
     <string name="feed_load_error_terminated">Autorov račun je ukinut.
@@ -637,7 +637,7 @@
     <string name="high_quality_larger">Visoka kvaliteta (veća)</string>
     <string name="seekbar_preview_thumbnail_title">Pregled sličica premotavanja</string>
     <string name="no_dir_yet">Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje</string>
-    <string name="comments_are_disabled">Komentari su onemogućeni</string>
+    <string name="comments_are_disabled">Komentari su isključeni</string>
     <string name="mark_as_watched">Označi kao pogledano</string>
     <string name="feed_load_error_fast_unknown">Način rada brzog feeda ne pruža više informacija o ovome.</string>
     <string name="metadata_privacy_internal">Interno</string>
@@ -646,15 +646,15 @@
     <string name="service_provides_reason">%s pruža ovaj razlog:</string>
     <string name="processing_may_take_a_moment">Obrada u tijeku … Može malo potrajati</string>
     <string name="main_page_content_swipe_remove">Za ukljanjanje stavki povuci ih</string>
-    <string name="show_image_indicators_title">Prikazati indikatore slike</string>
+    <string name="show_image_indicators_title">Prikaži indikatore slike</string>
     <plurals name="download_finished_notification">
         <item quantity="one">Preuzimanje je gotovo</item>
         <item quantity="few">%s preuzimanja su gotova</item>
         <item quantity="other">%s preuzimanja su gotova</item>
     </plurals>
     <string name="start_main_player_fullscreen_title">Pokreni glavni player u cjeloekranskom prikazu</string>
-    <string name="enqueue_next_stream">Reproduciraj sljedeći</string>
-    <string name="enqueued_next">Sljedeći u popisu izvođenja</string>
+    <string name="enqueue_next_stream">Dodaj u popis kao sljedeći</string>
+    <string name="enqueued_next">Dodano u popis kao sljedeći</string>
     <string name="show_image_indicators_summary">Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju</string>
     <plurals name="deleted_downloads_toast">
         <item quantity="one">Izbrisano %1$s preuzimanje</item>
@@ -680,7 +680,7 @@
     <string name="streams_notification_channel_name">Novi videozapisi</string>
     <string name="enable_streams_notifications_summary">Obavijesti novih streamova od pretplaćenih kanala</string>
     <string name="delete_downloaded_files_confirm">Želiš li izbrisati sve preuzete datoteke\?</string>
-    <string name="notifications_disabled">Obavijesti su onemogućene</string>
+    <string name="notifications_disabled">Obavijesti su isljučene</string>
     <string name="you_successfully_subscribed">Pretplatio/la si se na ovaj kanal</string>
     <string name="enumeration_comma">,</string>
     <string name="toggle_all">Uključi/isključi sve</string>
@@ -695,16 +695,16 @@
     <string name="streams_notifications_network_title">Potrebna mrežna veza</string>
     <string name="get_notified">Primaj obavijesti</string>
     <string name="no_appropriate_file_manager_message">Za ovu radnju nije pronađen odgovarajući upravljač datoteka.
-\nInstaliraj upravljač datoteka ili pokušaj onemogućiti „%s” u postavkama preuzimanja</string>
+\nInstaliraj upravljač datoteka ili pokušaj isključiti „%s” u postavkama preuzimanja</string>
     <string name="detail_pinned_comment_view_description">Prikvačeni komentar</string>
     <string name="show_crash_the_player_summary">Prikazuje opciju prekida rada kad se player koristi</string>
     <string name="show_crash_the_player_title">Prikaži „Prekini rad playera”</string>
     <string name="progressive_load_interval_exoplayer_default">ExoPlayer standard</string>
     <string name="percent">Posto</string>
     <string name="semitone">Poluton</string>
-    <string name="streams_not_yet_supported_removed">Streamovi koje program za preuzimanje još ne podržava se ne prikazuju</string>
-    <string name="selected_stream_external_player_not_supported">Vanjski playeri ne podržavaju odabrani stream</string>
-    <string name="progressive_load_interval_summary">Promijeni veličinu intervala učitavanja (trenutačno %s). Niža vrijednost može ubrzati početno učitavanje videa. Promjene zahtijevaju ponovno pokretanje playera</string>
+    <string name="streams_not_yet_supported_removed">Streamovi koje aplikacija za preuzimanje još ne podržava se ne prikazuju</string>
+    <string name="selected_stream_external_player_not_supported">Eksterni playeri ne podržavaju odabrani stream</string>
+    <string name="progressive_load_interval_summary">Promijenite veličinu intervala učitavanja progresivnog sadržaja (trenutno %s). Niža vrijednost može ubrzati učitavanje</string>
     <plurals name="new_streams">
         <item quantity="one">%s novi stream</item>
         <item quantity="few">%s nova streama</item>
@@ -713,9 +713,112 @@
     <string name="progressive_load_interval_title">Veličina intervala učitavanja reprodukcije</string>
     <string name="unknown_format">Nepoznat format</string>
     <string name="unknown_quality">Nepoznata kvaliteta</string>
-    <string name="no_audio_streams_available_for_external_players">Nijedan stream audiosnimaka nije dostupan za vanjske playere</string>
-    <string name="no_video_streams_available_for_external_players">Nijedan video stream nije dostupan za vanjske playere</string>
-    <string name="select_quality_external_players">Odaberi kvalitetu za vanjske playere</string>
+    <string name="no_audio_streams_available_for_external_players">Nema audio prijenosa za eksterne playere</string>
+    <string name="no_video_streams_available_for_external_players">Nema video prijenosa za eksterne playere</string>
+    <string name="select_quality_external_players">Odaberi kvalitetu za eksterne playere</string>
     <string name="no_appropriate_file_manager_message_android_10">Za ovu radnju nije pronađen odgovrajući upravljač datoteka.
 \nInstaliraj „Storage Access Framework” kompatibilni upravljač datoteka</string>
+    <string name="settings_category_exoplayer_summary">Uredite neke ExoPlayer postavke. Ove promjene zahtjevaju ponovo pokretanje aplikacije</string>
+    <string name="right_gesture_control_title">Desna gesta</string>
+    <string name="left_gesture_control_summary">Odaberite gestu za lijevu polovicu zaslona</string>
+    <string name="left_gesture_control_title">Lijeva gesta</string>
+    <string name="brightness">Svjetlina</string>
+    <string name="settings_category_exoplayer_title">ExoPlayer postavke</string>
+    <string name="notification_actions_summary_android13">Uredite svaku radnju obavijesti ispod tako da je dodirnete. Prve tri akcije (reprodukcija/pauza, prethodna i sljedeća) postavlja sustav i ne mogu se prilagoditi.</string>
+    <string name="right_gesture_control_summary">Odaberite gestu za desnu polovicu zaslona</string>
+    <string name="volume">Glasnoća</string>
+    <string name="none">Ništa</string>
+    <string name="feed_show_watched">Pregledano</string>
+    <string name="feed_show_partially_watched">Djelomično pregledano</string>
+    <string name="feed_show_upcoming">Najava</string>
+    <string name="sort">Razvrstaj</string>
+    <string name="use_exoplayer_decoder_fallback_title">Koristi razervnu funkciju ExoPlayer dekodera</string>
+    <string name="ignore_hardware_media_buttons_title">Ignoriranje hardverskih medijskih gumba</string>
+    <string name="ignore_hardware_media_buttons_summary">Korisno, na primjer, ako koristite slušalice s pokvarenim fizičkim gumbima</string>
+    <string name="prefer_descriptive_audio_summary">Odaberite zvučni zapis s opisima za slabovidne osobe ako je dostupan</string>
+    <string name="prefer_original_audio_title">Preferiraj originalni zvuk</string>
+    <string name="prefer_original_audio_summary">Odaberite izvorni audio zapis bez obzira na jezik</string>
+    <string name="prefer_descriptive_audio_title">Preferirajte opisni zvuk</string>
+    <string name="select_audio_track_external_players">Odaberi audio snimku za eksterne playere</string>
+    <string name="unknown_audio_track">Nepoznato</string>
+    <string name="audio_track_name">%1$s %2$s</string>
+    <string name="no_streams">Nema prijenosa</string>
+    <string name="main_tabs_position_title">Položaj glavnih kartica</string>
+    <string name="main_tabs_position_summary">Premjesti glavni birač kartica dolje</string>
+    <string name="no_live_streams">Nema prijenosa uživo</string>
+    <string name="msg_failed_to_copy">Neuspjelo kopiranje u međuspremnik</string>
+    <string name="faq_title">Često postavljena pitanja</string>
+    <string name="faq_description">Ako imaš problema s korištenjem aplikacije, pogledaj odgovore na česta pitanja!</string>
+    <string name="faq">Pogledaj na web stranici</string>
+    <string name="play_queue_audio_track">Audio snimka: %s</string>
+    <string name="audio_track">Traka audio snimke</string>
+    <string name="duplicate_in_playlist">Zasivljene playliste već sadrže ovu stavku.</string>
+    <string name="unset_playlist_thumbnail">Poništi stalni prikaz sličica</string>
+    <string name="playlist_add_stream_success_duplicate">Duplikat je dodan %d put(a)</string>
+    <string name="card">Kartica</string>
+    <string name="app_update_unavailable_toast">Koristiš najnoviju NewPipe verziju</string>
+    <string name="app_update_available_notification_text">Dodirani za preuzimanje %s</string>
+    <string name="remove_duplicates">Ukloni duplikate</string>
+    <string name="remove_duplicates_title">Ukloniti duplikate?</string>
+    <string name="remove_duplicates_message">Želiš li ukloniti sve duple prijenose iz ove playliste?</string>
+    <string name="feed_hide_streams_title">Pokaži sljedeće prijenose</string>
+    <string name="feed_show_hide_streams">Pokaži/Sakrij prijenose</string>
+    <string name="feed_fetch_channel_tabs">Kartice za dohvaćanja kanala</string>
+    <string name="night_theme_available">Ova je opcija dostupna samo ako je %s odabrano za temu</string>
+    <string name="metadata_thumbnails">Minijature</string>
+    <string name="metadata_avatars">Avatari</string>
+    <string name="metadata_uploader_avatars">Avatari prenositelja</string>
+    <string name="metadata_subchannel_avatars">Avatari podkanala</string>
+    <string name="metadata_subscribers">Pretplatnici</string>
+    <string name="metadata_banners">Natpisi</string>
+    <string name="audio_track_present_in_video">Audio zapis bi već trebao biti prisutan u ovom prijenosu</string>
+    <string name="audio_track_type_original">original</string>
+    <string name="channel_tab_livestreams">Uživo</string>
+    <string name="channel_tab_videos">Videa</string>
+    <string name="channel_tab_tracks">Snimke</string>
+    <string name="channel_tab_shorts">Kratka videa</string>
+    <string name="audio_track_type_dubbed">sinkronizirano</string>
+    <string name="audio_track_type_descriptive">opisno</string>
+    <string name="channel_tab_albums">Albumi</string>
+    <string name="channel_tab_channels">Kanali</string>
+    <string name="channel_tab_playlists">Playliste</string>
+    <string name="show_channel_tabs_summary">Kartice koje se prikazuju na stranici kanala</string>
+    <string name="toggle_screen_orientation">Uključi/Isključi položaj ekrana</string>
+    <string name="channel_tab_about">Informacije</string>
+    <string name="show_channel_tabs">Kartice kanala</string>
+    <string name="toggle_fullscreen">Uključi cjeloekranski prikaz</string>
+    <string name="previous_stream">Prethodni prijenos</string>
+    <string name="next_stream">Sljedeći prijenos</string>
+    <string name="replay">Reproduciraj ponovo</string>
+    <string name="rewind">Natrag</string>
+    <string name="forward">Naprijed</string>
+    <string name="image_quality_title">Kvaliteta slike</string>
+    <string name="more_options">Više opcija</string>
+    <string name="image_quality_summary">Odaberi kvalitetu slika i da li uopće učitati slike kako bi se smanjilo korištenje podataka i memorije. Promjene brišu predmemoriju slika u memoriji i na disku – %s</string>
+    <string name="image_quality_none">Ne učitavaj slike</string>
+    <string name="image_quality_low">Niska kvaliteta</string>
+    <string name="image_quality_medium">Srednja kvaliteta</string>
+    <string name="image_quality_high">Visoka kvaliteta</string>
+    <string name="question_mark">\?</string>
+    <string name="share_playlist_with_titles_message">Dijeli playlistu s detaljima kao što su ime playliste i naslovi videa ili kao jednostavan popis URL-ova videa</string>
+    <string name="share_playlist_with_titles">Dijeli s naslovima</string>
+    <string name="share_playlist_with_list">Dijeli popis URL-ova</string>
+    <string name="video_details_list_item">– %1$s: %2$s</string>
+    <string name="show_more">Pokaži više</string>
+    <string name="show_less">Pokaži manje</string>
+    <string name="fast_mode">Brzi modus</string>
+    <string name="loading_metadata_title">Učitavanje metapodataka …</string>
+    <string name="duration">Trajanje</string>
+    <string name="import_subscriptions_hint">Uvezi ili izvezi pretplate iz izbornika s 3 točke</string>
+    <string name="open_play_queue">Otvori popis reprodukcije</string>
+    <string name="play">Reproduciraj</string>
+    <string name="share_playlist">Dijeli playlistu</string>
+    <string name="share_playlist_content_details">%1$s
+\n%2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s odgovor</item>
+        <item quantity="few">%s odgovora</item>
+        <item quantity="other">%s odgovora</item>
+    </plurals>
+    <string name="disable_media_tunneling_automatic_info">Tuneliranje medija je standardno deaktivirano na tvom uređaju jer je poznato da model tvog uređaja to ne podržava.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 0b3b10235..58428df59 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -569,7 +569,7 @@
     <string name="processing_may_take_a_moment">Feldolgozás… Ez eltarthat egy ideig</string>
     <string name="enable_disposed_exceptions_summary">Az eltávolítás utáni, fragment vagy activity életcikluson kívüli, nem kézbesíthető Rx kivételek jelentésének kényszerítése</string>
     <string name="show_original_time_ago_title">Eredeti „ennyi ideje” megjelenítése az elemeken</string>
-    <string name="disable_media_tunneling_summary">Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor</string>
+    <string name="disable_media_tunneling_summary">Tiltsa le a médiacsatornázást, ha fekete képernyőt vagy akadozást tapasztal videólejátszáskor.</string>
     <string name="show_image_indicators_summary">Picasso színes szalagok megjelenítése a képek fölött, megjelölve a forrásukat: piros a hálózathoz, kék a lemezhez, zöld a memóriához</string>
     <string name="downloads_storage_ask_summary_no_saf_notice">Minden letöltésnél meg fogja kérdezni, hogy hova mentse el</string>
     <string name="choose_instance_prompt">Válasszon egy példányt</string>
@@ -762,4 +762,55 @@
     <string name="main_tabs_position_summary">Kezdőlap választó alulra helyezése</string>
     <string name="no_live_streams">Nincs élő adatfolyam</string>
     <string name="no_streams">Nincs adatfolyam</string>
+    <string name="notification_actions_summary_android13">Az alábbi értesítési műveletek szerkesztéséhez koppintson rá. Az első három műveletet (lejátszás/szünet, előző és következő) a rendszer állítja be, és nem szabhatók testre.</string>
+    <string name="feed_fetch_channel_tabs">Csatornalapok lekérése</string>
+    <string name="feed_fetch_channel_tabs_summary">A hírcsatorna frissítésekor lekérendő lapok. Ennek az opciónak nincs hatása, ha egy csatorna frissítése gyors módban történik.</string>
+    <string name="metadata_thumbnails">Miniatűrök</string>
+    <string name="metadata_uploader_avatars">Feltöltő avatarjai</string>
+    <string name="metadata_subchannel_avatars">Alcsatorna avatarok</string>
+    <string name="metadata_avatars">Avatarok</string>
+    <string name="metadata_banners">Bannerek</string>
+    <string name="metadata_subscribers">Feliratkozók</string>
+    <string name="channel_tab_channels">Csatornák</string>
+    <string name="channel_tab_playlists">Lejátszási listák</string>
+    <string name="channel_tab_albums">Albumok</string>
+    <string name="channel_tab_about">Névjegy</string>
+    <string name="show_channel_tabs">Csatorna fülek</string>
+    <string name="show_channel_tabs_summary">Milyen lapok jelennek meg a csatornaoldalakon</string>
+    <string name="open_play_queue">Lejátszási sor megnyitása</string>
+    <string name="toggle_screen_orientation">Képernyő tájolásának váltása</string>
+    <string name="toggle_fullscreen">Teljes képernyőre váltás</string>
+    <string name="next_stream">Következő közvetítés</string>
+    <string name="previous_stream">Előző közvetítés</string>
+    <string name="play">Lejátszás</string>
+    <string name="replay">Visszajátszás</string>
+    <string name="more_options">További opciók</string>
+    <string name="duration">Időtartam</string>
+    <string name="rewind">Visszatekerés</string>
+    <string name="forward">Előre</string>
+    <string name="image_quality_title">Képminőség</string>
+    <string name="image_quality_summary">Az adat- és memóriahasználat csökkentése érdekében válassza ki a képek minőségét valamint azt, hogy a képek egyáltalán betöltésre kerüljenek. A változtatások törlik a memóriában és a lemezen lévő képgyorsítótárat – %s</string>
+    <string name="image_quality_none">Ne töltsön be képeket</string>
+    <string name="image_quality_low">Alacsony minőség</string>
+    <string name="image_quality_medium">Közepes minőség</string>
+    <string name="image_quality_high">Magas minőségű</string>
+    <string name="question_mark">\?</string>
+    <string name="share_playlist">Lejátszási lista megosztása</string>
+    <string name="share_playlist_with_titles_message">Lejátszási lista megosztása olyan részletekkel, mint például a lejátszási lista neve és a videó címe, vagy a videó URL-jei egyszerű listájaként</string>
+    <string name="share_playlist_with_titles">Megosztás címekkel</string>
+    <string name="share_playlist_content_details">%1$s
+\n%2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s válasz</item>
+        <item quantity="other">%s válaszok</item>
+    </plurals>
+    <string name="show_more">Továbbiak</string>
+    <string name="show_less">Mutass kevesebbet</string>
+    <string name="loading_metadata_title">Metaadatok betöltése…</string>
+    <string name="share_playlist_with_list">URL-lista megosztása</string>
+    <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="channel_tab_videos">Videók</string>
+    <string name="channel_tab_tracks">Dalok</string>
+    <string name="channel_tab_shorts">Rövidek</string>
+    <string name="channel_tab_livestreams">Élő</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 2dc9ecdea..1bab98066 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -4,23 +4,23 @@
     <string name="upload_date_text">Dipublikasikan pada %1$s</string>
     <string name="install">Pasang</string>
     <string name="cancel">Batal</string>
-    <string name="open_in_browser">Buka di browser</string>
+    <string name="open_in_browser">Buka di peramban</string>
     <string name="share">Bagikan</string>
     <string name="download">Unduh</string>
-    <string name="search">Telusuri</string>
+    <string name="search">Telusur</string>
     <string name="settings">Pengaturan</string>
     <string name="share_dialog_title">Bagikan dengan</string>
     <string name="use_external_video_player_title">Gunakan pemutar video eksternal</string>
     <string name="use_external_audio_player_title">Gunakan pemutar audio eksternal</string>
-    <string name="download_path_title">Folder unduhan Video</string>
+    <string name="download_path_title">Folder unduhan video</string>
     <string name="download_path_summary">Berkas video yang diunduh akan disimpan di sini</string>
     <string name="download_path_dialog_title">Pilih folder unduhan untuk berkas video</string>
-    <string name="download_path_audio_title">Folder unduhan Audio</string>
+    <string name="download_path_audio_title">Folder unduhan audio</string>
     <string name="download_path_audio_summary">Berkas audio yang diunduh akan disimpan di sini</string>
     <string name="download_path_audio_dialog_title">Pilih folder unduhan untuk berkas audio</string>
     <string name="default_resolution_title">Pilih kualitas</string>
     <string name="play_with_kodi_title">Putar dengan Kodi</string>
-    <string name="kore_not_found">Instal aplikasi Kode yang hilang\?</string>
+    <string name="kore_not_found">Pasang aplikasi Kore yang hilang?</string>
     <string name="show_play_with_kodi_title">Tampilkan opsi \"Putar dengan Kodi\"</string>
     <string name="show_play_with_kodi_summary">Tampilkan opsi untuk memutar video via Kodi</string>
     <string name="play_audio">Audio</string>
@@ -66,7 +66,7 @@
     <string name="start">Mulai</string>
     <string name="pause">Jeda</string>
     <string name="checksum">Ceksum</string>
-    <string name="ok">OK</string>
+    <string name="ok">Oke</string>
     <string name="msg_name">Nama berkas</string>
     <string name="msg_error">Galat</string>
     <string name="msg_running">NewPipe Sedang Mengunduh</string>
@@ -74,7 +74,7 @@
     <string name="msg_wait">Mohon tunggu…</string>
     <string name="msg_copied">Disalin ke papan klip</string>
     <string name="no_available_dir">Silakan pilih folder unduhan di pengaturan</string>
-    <string name="no_player_found">Pemutar stream tidak ada. Pasang VLC?</string>
+    <string name="no_player_found">Pemutar stream tidak ditemukan. Pasang VLC?</string>
     <string name="app_ui_crash">App/UI rusak</string>
     <string name="info_labels">Apa:\\nPermintaan:\\nBahasa Konten:\\nNegara Konten:\\nBahasa Apl:\\nLayanan:\\nWaktu GMT:\\nPaket:\\nVersi:\\nVersi OS:</string>
     <string name="msg_threads">Thread</string>
@@ -85,19 +85,19 @@
     <string name="short_thousand">r</string>
     <string name="short_million">J</string>
     <string name="short_billion">T</string>
-    <string name="open_in_popup_mode">Buka dalam mode popup</string>
+    <string name="open_in_popup_mode">Buka di mode tampilan kecil</string>
     <string name="msg_popup_permission">Izin ini dibutuhkan untuk
 \nmembuka di mode sembul</string>
     <string name="popup_playing_toast">Memutar dalam mode sembul</string>
     <string name="disabled">Dinonaktifkan</string>
     <string name="default_video_format_title">Format video</string>
-    <string name="default_popup_resolution_title">Pilih kualitas mode popup</string>
+    <string name="default_popup_resolution_title">Kualitas popup bawaan</string>
     <string name="show_higher_resolutions_title">Tampilkan kualitas yang lebih tinggi</string>
     <string name="show_higher_resolutions_summary">Hanya perangkat tertentu saja yang bisa memutar video 2K/4K</string>
     <string name="controls_background_title">Latar Belakang</string>
-    <string name="controls_popup_title">Munculan</string>
+    <string name="controls_popup_title">Sembulan</string>
     <string name="clear">Bersihkan</string>
-    <string name="use_external_video_player_summary">Menghapus audio pada beberapa resolusi</string>
+    <string name="use_external_video_player_summary">Menghapus audio di beberapa resolusi</string>
     <string name="popup_remember_size_pos_title">Ingat properti popup</string>
     <string name="popup_remember_size_pos_summary">Ingat ukuran dan posisi terakhir popup</string>
     <string name="show_search_suggestions_title">Saran pencarian</string>
@@ -237,11 +237,11 @@
     <string name="resize_fill">Isi</string>
     <string name="resize_zoom">Perbesar</string>
     <string name="caption_auto_generated">Otomatis dibuat</string>
-    <string name="no_player_found_toast">Pemutar stream tidak ada (pasang VLC untuk memutarnya).</string>
+    <string name="no_player_found_toast">Pemutar stream ditemukan (kamu bisa pasang VLC untuk memutarnya).</string>
     <string name="controls_download_desc">Unduh berkas stream</string>
     <string name="subscription_change_failed">Tidak bisa mengubah langganan</string>
     <string name="show_info">Tampilkan info</string>
-    <string name="controls_add_to_playlist_title">Tambahkan Ke</string>
+    <string name="controls_add_to_playlist_title">Tambah Ke</string>
     <string name="clear_views_history_title">Hapus riwayat tontonan</string>
     <string name="clear_views_history_summary">Hapus riwayat video yang diputar dan posisi pemutaran</string>
     <string name="delete_view_history_alert">Hapus seluruh riwayat tontonan\?</string>
@@ -254,7 +254,7 @@
     <string name="one_item_deleted">1 item dihapus.</string>
     <string name="tab_bookmarks">Daftar Putar</string>
     <string name="auto_queue_title">Putar otomatis video berikutnya</string>
-    <string name="channel_unsubscribed">Berhenti berlanggan channel</string>
+    <string name="channel_unsubscribed">Berhenti berlangganan saluran</string>
     <string name="subscription_update_failed">Tidak bisa memperbarui langganan</string>
     <string name="tab_subscriptions">Langganan</string>
     <string name="use_inexact_seek_title">Gunakan tinjau cepat tak pasti</string>
@@ -322,7 +322,7 @@
     <string name="minimize_on_exit_none_description">Nihil</string>
     <string name="minimize_on_exit_background_description">Minimalkan ke pemutar latar belakang</string>
     <string name="minimize_on_exit_popup_description">Minimalkan ke pemutar popup</string>
-    <string name="unsubscribe">Berhenti Berlanggan</string>
+    <string name="unsubscribe">Berhenti Berlangganan</string>
     <string name="tab_choose">Pilih Tab</string>
     <string name="theme_title">Tema</string>
     <string name="settings_category_updates_title">Pembaruan</string>
@@ -534,7 +534,7 @@
     <string name="notification_action_shuffle">Aduk</string>
     <string name="notification_action_repeat">Ulangi</string>
     <string name="notification_actions_at_most_three">Anda bisa memilih hingga tiga tindakan untuk ditampilkan dalam notifikasi ringkas!</string>
-    <string name="notification_actions_summary">Sentuh untuk menyunting tindakan notifikasi di bawah. Pilih hingga tiga diantaranya untuk ditampilkan dalam notifikasi ringkas dengan menggunakan kotak centang di sebelah kanan</string>
+    <string name="notification_actions_summary">Sentuh untuk menyunting tindakan notifikasi di bawah. Pilih hingga tiga diantaranya untuk ditampilkan dalam notifikasi ringkas dengan menggunakan kotak centang di sebelah kanan.</string>
     <string name="notification_action_4_title">Tombol tindakan kelima</string>
     <string name="notification_action_3_title">Tombol tindakan keempat</string>
     <string name="notification_action_2_title">Tombol tindakan ketiga</string>
@@ -585,7 +585,7 @@
     <string name="night_theme_title">Tema malam</string>
     <string name="download_has_started">Pengunduhan dimulai</string>
     <string name="show_channel_details">Tampilkan detail channel</string>
-    <string name="disable_media_tunneling_summary">Nonaktifkan terowongan media (tunnel) jiaka anda mengalami sebuah layar hitam atau kerusakan dalam memutar video.</string>
+    <string name="disable_media_tunneling_summary">Nonaktifkan terowongan media (tunnel) jika anda mengalami sebuah layar hitam atau kerusakan dalam memutar video.</string>
     <string name="disable_media_tunneling_title">Nonaktifkan terowongan media (tunnel)</string>
     <string name="metadata_privacy_internal">Internal</string>
     <string name="metadata_privacy_private">Privasi</string>
@@ -646,7 +646,7 @@
     <string name="feed_new_items">Item feed baru</string>
     <string name="show_crash_the_player_title">Tampilkan \"Mogokkan pemutar\"</string>
     <string name="show_crash_the_player_summary">Menampilkan opsi penghentian ketika menggunakan pemain video</string>
-    <string name="crash_the_player">Hentikan pemain video</string>
+    <string name="crash_the_player">Hentikan pemutar video</string>
     <string name="error_report_channel_description">Notifikasi untuk melaporkan kegalatan</string>
     <string name="error_report_channel_name">Notifikasi laporan kegalatan</string>
     <string name="error_report_notification_toast">Sebuah kegalatan terjadi, lihat notifikasinya</string>
@@ -793,4 +793,10 @@
     <string name="share_playlist_with_titles_message">Bagikan daftar putar dengan detail seperti nama daftar putar dan judul video atau sebagai daftar video URL yang sederhana</string>
     <string name="metadata_banners">Banner</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="notification_actions_summary_android13">Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi.</string>
+    <string name="show_less">Tampilkan lebih sedikit</string>
+    <string name="show_more">Tampilkan lebih banyak</string>
+    <plurals name="replies">
+        <item quantity="other">%s balasan</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 71b656234..17cdb1c4b 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -549,7 +549,7 @@
     <string name="clear_queue_confirmation_description">La coda del lettore attivo sarà sostituita</string>
     <string name="clear_queue_confirmation_title">Chiedi prima di svuotare la coda</string>
     <string name="clear_queue_confirmation_summary">Cambiare tipo di riproduzione potrebbe sostituire gli elementi in coda</string>
-    <string name="notification_actions_summary">Le azioni dei pulsanti della notifica possono essere modificate qua sotto. Selezionane fino a 3, utilizzando le caselle di controllo sulla destra. Verranno visualizzate nella notifica compatta</string>
+    <string name="notification_actions_summary">Le azioni dei pulsanti della notifica possono essere modificate qua sotto. Selezionane fino a tre da mostrare nella notifica compatta, usando le caselle di controllo sulla destra.</string>
     <string name="notification_action_4_title">Azione pulsante 5</string>
     <string name="notification_action_3_title">Azione pulsante 4</string>
     <string name="notification_action_2_title">Azione pulsante 3</string>
@@ -779,7 +779,7 @@
     <string name="metadata_subscribers">Iscritti</string>
     <string name="show_channel_tabs_summary">Quali schede mostrare nelle pagine del canale</string>
     <string name="show_channel_tabs">Schede canale</string>
-    <string name="channel_tab_shorts">Short</string>
+    <string name="channel_tab_shorts">Shorts</string>
     <string name="loading_metadata_title">Caricamento metadati…</string>
     <string name="feed_fetch_channel_tabs">Recupera schede del canale</string>
     <string name="channel_tab_about">Informazioni</string>
@@ -819,4 +819,12 @@
     <string name="share_playlist">Condividi playlist</string>
     <string name="share_playlist_with_titles_message">Condividi la playlist con dettagli come il suo nome e i titoli video o come un semplice elenco di URL video</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s risposta</item>
+        <item quantity="many">%s risposte</item>
+        <item quantity="other">%s risposte</item>
+    </plurals>
+    <string name="show_more">Mostra altro</string>
+    <string name="notification_actions_summary_android13">Le azioni dei pulsanti della notifica possono essere modificate qua sotto. Le prime tre (riproduci/pausa, precedente e successivo) sono impostate dal sistema e non possono essere cambiate.</string>
+    <string name="show_less">Mostra meno</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 796c6d650..93b52997c 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -531,7 +531,7 @@
     <string name="unsupported_url_dialog_message">URL を認識できませんでした。他のアプリで開きますか?</string>
     <string name="notification_scale_to_square_image_summary">通知に表示されるサムネイルを 16:9 から正方形にします</string>
     <string name="notification_scale_to_square_image_title">サムネイルを正方形にする</string>
-    <string name="notification_actions_summary">以下をタップして通知のアクションを編集します。右側にあるチェックボックスを使用してコンパクトな通知に表示するものを 3 つまで選択します</string>
+    <string name="notification_actions_summary">以下をタップして通知のアクションを編集します。右側にあるチェックボックスを使用してコンパクトな通知に表示するものを3つまで選択します。</string>
     <string name="notification_actions_at_most_three">コンパクトな通知に表示されるアクションは 3 つまで選ぶことができます!</string>
     <string name="notification_action_4_title">5 番目のアクションボタン</string>
     <string name="notification_action_3_title">4 番目のアクションボタン</string>
@@ -793,4 +793,10 @@
     <string name="share_playlist">プレイリストを共有</string>
     <string name="share_playlist_with_titles_message">プレイリスト名やビデオタイトルなどの詳細を含むプレイリスト、またはビデオURLのみのシンプルなリストとしてプレイリストを共有します</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="other">%sの返信</item>
+    </plurals>
+    <string name="show_more">もっと見る</string>
+    <string name="show_less">表示を少なくする</string>
+    <string name="notification_actions_summary_android13">以下の通知アクションをタップして編集します。 最初の3つのアクション (再生/一時停止、前へ、次へ)はシステムによって設定されており、カスタマイズすることはできません。</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml
index a6b3daec9..ec598114d 100644
--- a/app/src/main/res/values-kn/strings.xml
+++ b/app/src/main/res/values-kn/strings.xml
@@ -1,2 +1,43 @@
 <?xml version="1.0" encoding="utf-8"?>
-<resources></resources>
\ No newline at end of file
+<resources>
+    <string name="controls_add_to_playlist_title">ಗೆ ಸೇರಿಸಿ</string>
+    <string name="show_info">ಮಾಹಿತಿಯನ್ನು ತೋರಿಸಿ</string>
+    <string name="channel_unsubscribed">ಚಾನಲ್ ಅನ್‌ಸಬ್‌ಸ್ಕ್ರೈಬ್ ಮಾಡಲಾಗಿದೆ</string>
+    <string name="open_in_popup_mode">ಪಾಪ್‌ಅಪ್ ಮೋಡ್‌ನಲ್ಲಿ ತೆರೆಯಿರಿ</string>
+    <string name="did_you_mean">ನೀವು \"%1$s\" ಎಂದು ಅರ್ಥೈಸಿದ್ದೀರಾ?</string>
+    <string name="share_dialog_title">ಬಳಸಿ ಹಂಚಿಕೊಳ್ಳಿ</string>
+    <string name="download_path_dialog_title">ವೀಡಿಯೊ ಫೈಲ್‌ಗಳಿಗಾಗಿ ಡೌನ್‌ಲೋಡ್ ಫೋಲ್ಡರ್ ಆಯ್ಕೆಮಾಡಿ</string>
+    <string name="subscribed_button_title">ಸಬ್‌ಸ್ಕ್ರೈಬ್ ಮಾಡಲಾಗಿದೆ</string>
+    <string name="download_path_audio_summary">ಡೌನ್‌ಲೋಡ್ ಮಾಡಿದ ಆಡಿಯೊ ಫೈಲ್‌ಗಳನ್ನು ಇಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ</string>
+    <string name="use_external_audio_player_title">ಬಾಹ್ಯ ಆಡಿಯೊ ಪ್ಲೇಯರ್ ಬಳಸಿ</string>
+    <string name="subscription_change_failed">ಚಂದಾದಾರಿಕೆಯನ್ನು ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ</string>
+    <string name="main_bg_subtitle">ಪ್ರಾರಂಭಿಸಲು ಭೂತಗನ್ನಡಿಯನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ.</string>
+    <string name="use_external_video_player_summary">ಕೆಲವು ರೆಸಲ್ಯೂಶನ್‌ಗಳಲ್ಲಿ ಆಡಿಯೊವನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ</string>
+    <string name="upload_date_text">%1$s ರಂದು ಪ್ರಕಟಿಸಲಾಗಿದೆ</string>
+    <string name="mark_as_watched">ವೀಕ್ಷಿಸಲಾಗಿದೆ ಎಂದು ಗುರುತಿಸಿ</string>
+    <string name="no_player_found_toast">ಯಾವುದೇ ಸ್ಟ್ರೀಮ್ ಪ್ಲೇಯರ್ ಕಂಡುಬಂದಿಲ್ಲ (ಅದನ್ನು ಪ್ಲೇ ಮಾಡಲು ನೀವು VLC ಅನ್ನು ಸ್ಥಾಪಿಸಬಹುದು).</string>
+    <string name="ok">ಸರಿ</string>
+    <string name="open_in_browser">ಬ್ರೌಸರ್‌ನಲ್ಲಿ ತೆರೆಯಿರಿ</string>
+    <string name="controls_popup_title">ಪಾಪ್ಅಪ್</string>
+    <string name="download">ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ</string>
+    <string name="search_showing_result_for">ಇದಕ್ಕೆ ಫಲಿತಾಂಶಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತಿದೆ: %s</string>
+    <string name="open_with">ಇದರೊಂದಿಗೆ ತೆರೆಯಿರಿ</string>
+    <string name="download_path_summary">ಡೌನ್‌ಲೋಡ್ ಮಾಡಿದ ವೀಡಿಯೊ ಫೈಲ್‌ಗಳನ್ನು ಇಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ</string>
+    <string name="controls_download_desc">ಸ್ಟ್ರೀಮ್ ಫೈಲ್ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ</string>
+    <string name="cancel">ರದ್ದುಗೊಳಿಸಿ</string>
+    <string name="download_path_audio_title">ಆಡಿಯೋ ಡೌನ್‌ಲೋಡ್ ಫೋಲ್ಡರ್</string>
+    <string name="tab_bookmarks">ಬುಕ್ಮಾರ್ಕ್ ಮಾಡಲಾದ ಪ್ಲೇಪಟ್ಟಿಗಳು</string>
+    <string name="subscription_update_failed">ಚಂದಾದಾರಿಕೆಯನ್ನು ನವೀಕರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ</string>
+    <string name="subscribe_button_title">ಸಬ್‌ಸ್ಕ್ರೈಬ್</string>
+    <string name="search">ಹುಡುಕಿ</string>
+    <string name="tab_subscriptions">ಚಂದಾದಾರಿಕೆಗಳು</string>
+    <string name="settings">ಸೆಟ್ಟಿಂಗ್‌ಗಳು</string>
+    <string name="use_external_video_player_title">ಬಾಹ್ಯ ವೀಡಿಯೊ ಪ್ಲೇಯರ್ ಬಳಸಿ</string>
+    <string name="install">ಸ್ಥಾಪಿಸಿ</string>
+    <string name="no_player_found">ಯಾವುದೇ ಸ್ಟ್ರೀಮ್ ಪ್ಲೇಯರ್ ಕಂಡುಬಂದಿಲ್ಲ. VLC ಸ್ಥಾಪಿಸುವುದೇ?</string>
+    <string name="share">ಶೇರ್ ಮಾಡಿ</string>
+    <string name="download_path_title">ವೀಡಿಯೊ ಡೌನ್‌ಲೋಡ್ ಫೋಲ್ಡರ್</string>
+    <string name="tab_choose">ಟ್ಯಾಬ್ ಆಯ್ಕೆಮಾಡಿ</string>
+    <string name="controls_background_title">ಹಿನ್ನೆಲೆ</string>
+    <string name="unsubscribe">ಅನ್‌ಸಬ್‌ಸ್ಕ್ರೈಬ್ ಮಾಡಿ</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index 33fd41f59..8c6b88716 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -432,4 +432,8 @@
     <string name="dont_show">Jangan tunjuk</string>
     <string name="unmute">Nyahbisu</string>
     <string name="notification_colorize_summary">Minta Android menyesuaikan warna pemberitahuan sesuai dengan warna utama di thumbnail (perhatikan bahawa ini tidak tersedia dalam semua perangkat)</string>
+    <string name="autoplay_title">Auto-main</string>
+    <string name="enable_playback_resume_title">Sambung main</string>
+    <string name="error_unable_to_load_comments">Tidak boleh memuat komentar</string>
+    <string name="close">Tutup</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 1c8575d70..c1cb3db93 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -30,7 +30,7 @@
     <string name="dark_theme_title">Mørk</string>
     <string name="light_theme_title">Lys</string>
     <string name="download_dialog_title">Last ned</string>
-    <string name="show_next_and_similar_title">Vis \"Neste\" og \"Lignende\" -videoer</string>
+    <string name="show_next_and_similar_title">Vis «Neste» og «Lignende» -videoer</string>
     <string name="unsupported_url">Nettadressen støttes ikke</string>
     <string name="content_language_title">Forvalgt innholdsspråk</string>
     <string name="settings_category_video_audio_title">Video og lyd</string>
@@ -558,7 +558,7 @@
     <string name="clear_cookie_title">Tøm reCAPTCHA-kaker</string>
     <string name="recaptcha_cookies_cleared">reCAPTCHA-kaker har blitt slettet</string>
     <string name="clear_cookie_summary">Tøm kaker som NewPipe lagrer når du løser en reCAPTCHA</string>
-    <string name="youtube_restricted_mode_enabled_summary">YouTube tilbyr en \"Begrenset modus\" som skjuler potensielt modent innhold</string>
+    <string name="youtube_restricted_mode_enabled_summary">YouTube tilbyr «Begrenset modus» som skjuler innhold antatt å være for voksne</string>
     <string name="show_age_restricted_content_summary">Vis innhold som muligens er uegnet for barn fordi det har en aldersgrense (som 18+)</string>
     <string name="notification_colorize_summary">La Android tilpasse varselets farge i henhold til hovedfargen i miniatyrbildet (merk at dette ikke er tilgjengelig på alle enheter)</string>
     <string name="notification_colorize_title">Fargelegg merknad</string>
@@ -772,4 +772,30 @@
     <string name="no_streams">Ingen strømmer</string>
     <string name="channel_tab_channels">Kanaler</string>
     <string name="previous_stream">Forrige strøm</string>
+    <string name="show_more">Vis mer</string>
+    <plurals name="replies">
+        <item quantity="one">%s svar</item>
+        <item quantity="other">%s svar</item>
+    </plurals>
+    <string name="main_tabs_position_summary">Flytt hovedfanevelgeren til bunnen</string>
+    <string name="main_tabs_position_title">Hovedfaneposisjon</string>
+    <string name="channel_tab_videos">Videoer</string>
+    <string name="unknown_audio_track">Ukjent</string>
+    <string name="channel_tab_livestreams">Direkte</string>
+    <string name="rewind">Spol tilbake</string>
+    <string name="forward">Spol forover</string>
+    <string name="image_quality_title">Bildekvalitet</string>
+    <string name="show_less">Vis mindre</string>
+    <string name="share_playlist_with_titles">Del med titler</string>
+    <string name="share_playlist_with_list">Del nettadresse først</string>
+    <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="settings_category_exoplayer_title">ExoPlayer-innstillinger</string>
+    <string name="audio_track_name">%1$s %2$s</string>
+    <string name="audio_track_type_descriptive">beskrivende</string>
+    <string name="toggle_fullscreen">Veksle fullskjermsvising</string>
+    <string name="toggle_screen_orientation">Bytt skjermretning</string>
+    <string name="notification_actions_summary_android13">Rediger hver merknadshandling nedenfor ved å trykke på den. De første tre (spill av/pause, forrige, og neste) kan ikke tilpasses.</string>
+    <string name="use_exoplayer_decoder_fallback_title">Bruk ExoPlayers dekodings-tilbakefall</string>
+    <string name="settings_category_exoplayer_summary">Håndter noen ExoPlayer-innstillinger. Disse endringeren krever omstart av avspilleren.</string>
+    <string name="show_channel_tabs_summary">Hvilke faner som vises på kanalsidene</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index b49526071..b08e20202 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -216,7 +216,7 @@
     <string name="override_current_data">Dit overschrijft je huidige configuratie.</string>
     <string name="controls_download_desc">Streambestand downloaden</string>
     <string name="show_info">Informatie tonen</string>
-    <string name="tab_bookmarks">Bijgehouden afspeellijsten</string>
+    <string name="tab_bookmarks">Bijgehouden afspeellijsten</string>
     <string name="controls_add_to_playlist_title">Toevoegen aan</string>
     <string name="detail_drag_description">Versleep om de volgorde te wijzigen</string>
     <string name="create">Aanmaken</string>
@@ -374,7 +374,7 @@
     <string name="stop">Stop</string>
     <string name="max_retry_msg">Maximum aantal keer proberen</string>
     <string name="max_retry_desc">Maximum aantal pogingen voordat de download wordt geannuleerd</string>
-    <string name="pause_downloads_on_mobile">Pauzeren bij mobiele data verbinding</string>
+    <string name="pause_downloads_on_mobile">Pauzeren bij mobiele gegevens­verbinding</string>
     <string name="pause_downloads_on_mobile_desc">Handig voor wanneer u naar mobiel internet overschakelt, hoewel sommige downloads niet gepauzeerd kunnen worden</string>
     <string name="events">Gebeurtenissen</string>
     <string name="conferences">Conferenties</string>
@@ -386,7 +386,7 @@
     <string name="error_unable_to_load_comments">Kan reacties niet laden</string>
     <string name="close">Sluiten</string>
     <string name="downloads_storage_use_saf_summary">Het \'Storage Access Framework\' staat downloads naar een externe SD kaart toe</string>
-    <string name="settings_category_clear_data_title">Wis data</string>
+    <string name="settings_category_clear_data_title">Gegevens wissen</string>
     <string name="enable_playback_resume_title">Afspelen hervatten</string>
     <string name="enable_playback_resume_summary">Verder afspelen vanaf laatste positie</string>
     <string name="enable_playback_state_lists_title">Posities in lijst</string>
@@ -543,7 +543,7 @@
     <string name="notification_action_shuffle">Willekeurig afspelen</string>
     <string name="notification_action_repeat">Herhaal</string>
     <string name="notification_actions_at_most_three">Je kan maximaal drie acties selecteren om te tonen in de compacte notificatie!</string>
-    <string name="notification_actions_summary">Pas elke meldings­actie hieronder aan door er op te tikken. Selecteer tot drie acties die getoond worden in de compacte melding door gebruik te maken van de selectievakjes aan de rechterkant</string>
+    <string name="notification_actions_summary">Pas elke meldings­actie hieronder aan door er op te tikken. Selecteer tot drie acties die getoond worden in de compacte melding door gebruik te maken van de selectievakjes aan de rechterkant.</string>
     <string name="notification_action_4_title">Vijfde actieknop</string>
     <string name="notification_action_3_title">Vierde actieknop</string>
     <string name="notification_action_2_title">Derde actieknop</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">Afspeellijst delen</string>
     <string name="share_playlist_with_titles_message">Deel afspeellijst met details zoals afspeellijstnaam en videotitels of als een eenvoudige lijst met video-URL\'s</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s antwoord</item>
+        <item quantity="other">%s antwoorden</item>
+    </plurals>
+    <string name="show_more">Meer tonen</string>
+    <string name="show_less">Minder tonen</string>
+    <string name="notification_actions_summary_android13">Bewerk elke meldings­actie hieronder door erop te tikken. De eerste drie acties (afspelen/pauzeren, vorige en volgende) zijn ingesteld door het systeem en kunnen niet worden aangepast.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index fb2cfd675..89715cd84 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -18,7 +18,7 @@
         <item quantity="other">ମୋଟ %d ମିନିଟ୍</item>
     </plurals>
     <string name="app_language_title">ଆପ୍ ଭାଷା</string>
-    <string name="auto_device_theme_title">ସ୍ୱୟଂଚାଳିତ (ଡିଭାଇସ୍ ଥିମ୍)</string>
+    <string name="auto_device_theme_title">ସ୍ୱଚାଳିତ (ଡିଵାଇସ୍ ଥିମ୍)</string>
     <string name="metadata_privacy">ଗୋପନୀୟତା</string>
     <string name="name">ନାମ</string>
     <string name="play_queue_stream_detail">ବିବରଣୀ</string>
@@ -147,7 +147,7 @@
     <string name="settings_category_player_behavior_title">ବ୍ୟବହାର</string>
     <string name="settings_category_video_audio_title">ଵିଡ଼ିଓ ଓ ଅଡ଼ିଓ</string>
     <string name="start_main_player_fullscreen_summary">ମିନି ପ୍ଲେୟାରରେ ଭିଡିଓ ଆରମ୍ଭ କରନ୍ତୁ ନାହିଁ, କିନ୍ତୁ ଅଟୋ ଘୂର୍ଣ୍ଣନ ବନ୍ଦ ହୋଇଗଲେ ସିଧାସଳଖ ଫୁଲ୍ ସ୍କ୍ରିନ୍ ମୋଡ୍ କୁ ଯାଆନ୍ତୁ। ଫୁଲ୍ ସ୍କ୍ରିନ୍ ଛାଡି ଆପଣ ଏପର୍ଯ୍ୟନ୍ତ ମିନି ପ୍ଲେୟାରକୁ ପ୍ରବେଶ କରିପାରିବେ</string>
-    <string name="notification_actions_summary">ଏହା ଉପରେ ଟ୍ୟାପ୍ କରି ନିମ୍ନରେ ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟ ସଂପାଦନ କରନ୍ତୁ। ଡାହାଣରେ ଥିବା ଚେକ୍ ବକ୍ସ ବ୍ୟବହାର କରି କମ୍ପାକ୍ଟ ବିଜ୍ଞପ୍ତିରେ ଦେଖାଯିବାକୁ ସେମାନଙ୍କ ମଧ୍ୟରୁ ତିନୋଟି ପର୍ଯ୍ୟନ୍ତ ଚୟନ କରନ୍ତୁ</string>
+    <string name="notification_actions_summary">ଏହା ଉପରେ ଟ୍ୟାପ କରି ନିମ୍ନରେ ଦିଆଯାଇଥିବା ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟକୁ ସମ୍ପାଦନ କରନ୍ତୁ । ଡାହାଣ ପଟେ ଥିବା ଚେକବକ୍ସଗୁଡ଼ିକୁ ବ୍ୟବହାର କରି କମ୍ପାକ୍ଟ ବିଜ୍ଞପ୍ତିରେ ଦର୍ଶାଇବା ପାଇଁ ସେଗୁଡ଼ିକ ମଧ୍ୟରୁ ତିନିଟି ଚୟନ କରନ୍ତୁ ।</string>
     <string name="show_meta_info_summary">ଷ୍ଟ୍ରିମ୍ ସୃଷ୍ଟିକର୍ତ୍ତା, ଷ୍ଟ୍ରିମ୍ ବିଷୟବସ୍ତୁ କିମ୍ବା ଏକ ସନ୍ଧାନ ଅନୁରୋଧ ବିଷୟରେ ଅତିରିକ୍ତ ସୂଚନା ସହିତ ମେଟା ସୂଚନା ବାକ୍ସଗୁଡ଼ିକୁ ଲୁଚାଇବାକୁ ବନ୍ଦ କରନ୍ତୁ</string>
     <string name="show_age_restricted_content_summary">ପିଲାମାନଙ୍କ ପାଇଁ ସମ୍ଭବତ content ଅନୁପଯୁକ୍ତ ବିଷୟବସ୍ତୁ ଦେଖାନ୍ତୁ କାରଣ ଏହାର ବୟସ ସୀମା ଅଛି (ଯେପରିକି 18+)</string>
     <string name="restricted_video_no_stream">ଏହି ଭିଡିଓ ବୟସ-ସୀମିତ ଅଟେ ।
@@ -806,4 +806,11 @@
     <string name="share_playlist">ଖେଳ ତାଲିକା ସହଭାଗ କରନ୍ତୁ</string>
     <string name="share_playlist_with_titles_message">ପ୍ଲେ-ଲିଷ୍ଟ ନାମ ଏବଂ ଭିଡିଓ ଶୀର୍ଷକ କିମ୍ବା ଭିଡିଓ URLଗୁଡ଼ିକର ଏକ ସରଳ ତାଲିକା ଭାବରେ ବିବରଣୀ ସହିତ ପ୍ଲେ-ଲିଷ୍ଟ ଅଂଶୀଦାର କରନ୍ତୁ</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="show_more">ଅଧିକ ଦର୍ଶାନ୍ତୁ</string>
+    <string name="notification_actions_summary_android13">ଏହା ଉପରେ ଟ୍ୟାପ କରି ନିମ୍ନରେ ଦିଆଯାଇଥିବା ପ୍ରତ୍ୟେକ ବିଜ୍ଞପ୍ତି କାର୍ଯ୍ୟକୁ ସମ୍ପାଦନ କରନ୍ତୁ । ପ୍ରଥମ ତିନୋଟି କାର୍ଯ୍ୟ (ଖେଳ/ବିରତି, ପୂର୍ବବର୍ତ୍ତୀ ଏବଂ ପରବର୍ତ୍ତୀ) ତନ୍ତ୍ର ଦ୍ୱାରା ସେଟ କରାଯାଇଥାଏ ଏବଂ ଏହାକୁ ଇଚ୍ଛାରୂପଣ କରାଯାଇପାରିବ ନାହିଁ ।</string>
+    <plurals name="replies">
+        <item quantity="one">%s ଉତ୍ତର</item>
+        <item quantity="other">%s ଉତ୍ତରଗୁଡ଼ିକ</item>
+    </plurals>
+    <string name="show_less">କମ୍ ଦର୍ଶାନ୍ତୁ</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index e74e747f4..ee5b4aacc 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -602,7 +602,7 @@
     <string name="notification_action_buffering">ਬਫ਼ਰਿੰਗ</string>
     <string name="notification_action_shuffle">ਸ਼ਫਲ</string>
     <string name="notification_action_repeat">ਦੁਹਰਾਓ</string>
-    <string name="notification_actions_summary">ਹੇਠਾਂ ਹਰੇਕ ਨੋਟੀਫਿਕੇਸ਼ਨ ਕਾਰਵਾਈ ਤੇ ਨੱਪਦਿਆਂ ਇਹਨਾਂ ਨੂੰ ਬਦਲੋ। ਇਹਨਾਂ ਦੇ ਸੱਜੇ ਪਾਸੇ ਬਣੇ ਚੈੱਕਬਾਕਸ ਵਰਤਦਿਆਂ ਇਹਨਾਂ ਵਿਚੋਂ ਵੱਧ-ਤੋਂ-ਵੱਧ ਤਿੰਨ ਕਾਰਵਾਈਆਂ ਨੂੰ ਤੁਸੀਂ ਕੰਪੈਕਟ ਨੋਟੀਫਿਕੇਸ਼ਨ ਵਿੱਚ ਵਿਖਾਉਣ ਲਈ ਚੁਣ ਸਕਦੇ ਹੋ</string>
+    <string name="notification_actions_summary">ਹੇਠਾਂ ਹਰੇਕ ਨੋਟੀਫਿਕੇਸ਼ਨ ਕਾਰਵਾਈ ਤੇ ਨੱਪਦਿਆਂ ਇਹਨਾਂ ਨੂੰ ਬਦਲੋ। ਇਹਨਾਂ ਦੇ ਸੱਜੇ ਪਾਸੇ ਬਣੇ ਚੈੱਕਬਾਕਸ ਵਰਤਦਿਆਂ ਇਹਨਾਂ ਵਿਚੋਂ ਵੱਧ-ਤੋਂ-ਵੱਧ ਤਿੰਨ ਕਾਰਵਾਈਆਂ ਨੂੰ ਤੁਸੀਂ ਕੰਪੈਕਟ ਨੋਟੀਫਿਕੇਸ਼ਨ ਵਿੱਚ ਵਿਖਾਉਣ ਲਈ ਚੁਣ ਸਕਦੇ ਹੋ।</string>
     <string name="notification_actions_at_most_three">ਤੁਸੀਂ ਵੱਧ-ਤੋਂ-ਵੱਧ ਤਿੰਨ ਕਾਰਵਾਈਆਂ ਨੂੰ ਕੰਪੈਕਟ ਨੋਟੀਫਿਕੇਸ਼ਨ ਵਿੱਚ ਵਿਖਾਉਣ ਲਈ ਚੁਣ ਸਕਦੇ ਹੋ!</string>
     <string name="notification_action_4_title">ਪੰਜਵਾਂ ਕਾਰਵਾਈ ਬਟਨ</string>
     <string name="notification_action_3_title">ਚੌਥਾ ਕਾਰਵਾਈ ਬਟਨ</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">ਪਲੇਲਿਸਟ ਸਾਂਝੀ ਕਰੋ</string>
     <string name="share_playlist_with_titles_message">ਪਲੇਲਿਸਟ ਦੇ ਨਾਮ ਅਤੇ ਵੀਡੀਓ ਸਿਰਲੇਖਾਂ ਜਾਂ ਵੀਡੀਓ URL ਦੀ ਇੱਕ ਸਧਾਰਨ ਸੂਚੀ ਦੇ ਰੂਪ ਵਿੱਚ ਵੇਰਵਿਆਂ ਨਾਲ ਪਲੇਲਿਸਟ ਨੂੰ ਸਾਂਝਾ ਕਰੋ</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s ਜਵਾਬ</item>
+        <item quantity="other">%s ਜਵਾਬ</item>
+    </plurals>
+    <string name="show_more">ਹੋਰ ਵਿਖਾਓ</string>
+    <string name="notification_actions_summary_android13">ਇਸ \'ਤੇ ਟੈਪ ਕਰਕੇ ਹੇਠਾਂ ਹਰੇਕ ਸੂਚਨਾ ਕਾਰਵਾਈ ਨੂੰ ਸੰਪਾਦਿਤ ਕਰੋ। ਪਹਿਲੀਆਂ ਤਿੰਨ ਕਾਰਵਾਈਆਂ (ਪਲੇ/ਪੌਜ਼, ਪਿਛਲਾ ਅਤੇ ਅਗਲਾ) ਸਿਸਟਮ ਦੁਆਰਾ ਸੈੱਟ ਕੀਤੀਆਂ ਗਈਆਂ ਹਨ ਅਤੇ ਉਹਨਾਂ ਨੂੰ ਅਨੁਕੂਲਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ।</string>
+    <string name="show_less">ਘੱਟ ਦਿਖਾਓ</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 8c7a28ddc..2a7d80396 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -827,4 +827,13 @@
     <string name="share_playlist">Udostępnij playlistę</string>
     <string name="share_playlist_with_titles_message">Udostępnij playlistę ze szczegółami, takimi jak nazwa playlisty i tytuły wideo, lub jako prostą listę adresów URL wideo.</string>
     <string name="video_details_list_item">– %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s odpowiedź</item>
+        <item quantity="few">%s odpowiedzi</item>
+        <item quantity="many">%s odpowiedzi</item>
+        <item quantity="other">%s odpowiedzi</item>
+    </plurals>
+    <string name="show_more">Pokaż więcej</string>
+    <string name="show_less">Pokaż mniej</string>
+    <string name="notification_actions_summary_android13">Edytuj każdą poniższą akcję powiadomienia, naciskając ją. Pierwsze trzy akcje (odtwórz/wstrzymaj, poprzedni i następny) są ustawione przez system i nie można ich dostosować</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 76c6b43fe..cca254aea 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -554,7 +554,7 @@
     <string name="notification_action_nothing">Nada</string>
     <string name="notification_action_repeat">Repetir</string>
     <string name="notification_actions_at_most_three">Você pode selecionar até no máximo três botões para mostrar na notificação compacta!</string>
-    <string name="notification_actions_summary">Edite os botões de ação de notificação abaixo tocando em cada um. Selecione até três deles para serem mostrados na notificação compacta usando as caixas de seleção à direita</string>
+    <string name="notification_actions_summary">Edite os botões de ação de notificação abaixo tocando em cada um. Selecione até três deles para serem mostrados na notificação compacta usando as caixas de seleção à direita.</string>
     <string name="notification_action_4_title">Quinto botão de ação</string>
     <string name="notification_action_3_title">Quarto botão de ação</string>
     <string name="notification_action_2_title">Terceiro botão de ação</string>
@@ -818,4 +818,13 @@
     <string name="metadata_thumbnails">Miniaturas</string>
     <string name="duration">Duração</string>
     <string name="previous_stream">Transmissão anterior</string>
+    <string name="metadata_banners">Banners</string>
+    <string name="show_more">Mostrar mais</string>
+    <string name="notification_actions_summary_android13">Edite cada ação de notificação abaixo tocando nela. As três primeiras ações (reproduzir/pausar, anterior e seguinte) são definidas pelo sistema e não podem ser personalizadas.</string>
+    <plurals name="replies">
+        <item quantity="one">%s resposta</item>
+        <item quantity="many">%s respostas</item>
+        <item quantity="other">%s respostas</item>
+    </plurals>
+    <string name="show_less">Mostrar menos</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 335c989ed..0dae4fc78 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -554,7 +554,7 @@
     <string name="notification_action_shuffle">Baralhar</string>
     <string name="notification_action_repeat">Repetir</string>
     <string name="notification_actions_at_most_three">Pode selecionar, no máximo, três ações para mostrar na notificação compacta!</string>
-    <string name="notification_actions_summary">Edite cada ação de notificação abaixo tocando nela. Selecione até três delas para serem mostrados na notificação compacta a usar as caixas de seleção à direita</string>
+    <string name="notification_actions_summary">Edite cada ação de notificação abaixo, a tocar nela. Selecione até três delas para serem mostradas na notificação compacta, através das caixas de verificação à direita.</string>
     <string name="notification_action_4_title">Quinto botão de ação</string>
     <string name="notification_action_3_title">Quarto botão de ação</string>
     <string name="notification_action_2_title">Terceiro botão de ação</string>
@@ -819,4 +819,12 @@
     <string name="channel_tab_channels">Canais</string>
     <string name="previous_stream">Fluxo anterior</string>
     <string name="channel_tab_livestreams">Direto</string>
+    <string name="show_more">Mostrar mais</string>
+    <string name="notification_actions_summary_android13">Edite cada ação de notificação abaixo a tocar nela. As três primeiras ações (reproduzir/pausa, anterior e seguinte) são definidas pelo sistema e não podem ser personalizadas.</string>
+    <plurals name="replies">
+        <item quantity="one">%s resposta</item>
+        <item quantity="many">%s respostas</item>
+        <item quantity="other">%s respostas</item>
+    </plurals>
+    <string name="show_less">Mostrar menos</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index f20bc05b0..799166a89 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -73,7 +73,7 @@
     <string name="msg_running_detail">Toque para detalhes</string>
     <string name="msg_wait">Por favor aguarde…</string>
     <string name="msg_copied">Copiado para a área de transferência</string>
-    <string name="no_available_dir">Tem que definir, nas definições, uma pasta para as descargas</string>
+    <string name="no_available_dir">Pode definir, mais tarde, uma pasta para as descargas</string>
     <string name="ok">OK</string>
     <string name="msg_threads">Processos</string>
     <string name="msg_running">Descarga NewPipe</string>
@@ -107,9 +107,9 @@
     <string name="title_activity_about">Acerca de NewPipe</string>
     <string name="title_licenses">Licenças de terceiros</string>
     <string name="copyright" formatted="true">© %1$s de %2$s nos termos da %3$s</string>
-    <string name="tab_about">Sobre e FAQ</string>
+    <string name="tab_about">Acerca e FAQ</string>
     <string name="tab_licenses">Licenças</string>
-    <string name="app_description">Aplicação livre de reprodução de transmissões para Android.</string>
+    <string name="app_description">Aplicação de reprodução para Android.</string>
     <string name="view_on_github">Ver no GitHub</string>
     <string name="app_license_title">Licença do NewPipe</string>
     <string name="contribution_encouragement">Se tem ideias para: tradução, alterações de desenho, limpeza de código, ou alterações significativas no código fonte - todas as ajudas são bem-vindas. Quanto mais se faz, melhor ficará!</string>
@@ -133,7 +133,7 @@
     <string name="settings_category_history_title">Histórico e cache</string>
     <string name="undo">Desfazer</string>
     <string name="notification_channel_name">Notificação NewPipe</string>
-    <string name="notification_channel_description">Notificações para o reprodutor do NewPipe</string>
+    <string name="notification_channel_description">Notificações para o reprodutor NewPipe</string>
     <string name="search_no_results">Sem resultados</string>
     <string name="empty_list_subtitle">Aqui não há nada para ver</string>
     <string name="no_subscribers">Sem subscritores</string>
@@ -154,7 +154,7 @@
         <item quantity="many">%s vídeos</item>
         <item quantity="other">%s vídeos</item>
     </plurals>
-    <string name="settings_category_downloads_title">Transferências</string>
+    <string name="settings_category_downloads_title">Descargas</string>
     <string name="settings_file_charset_title">Carateres permitidos no nome dos ficheiros</string>
     <string name="settings_file_replacement_character_summary">Os carateres inválidos são substituídos por este valor</string>
     <string name="settings_file_replacement_character_title">Carácter de substituição</string>
@@ -180,7 +180,7 @@
     <string name="play_queue_stream_detail">Detalhes</string>
     <string name="play_queue_audio_settings">Definições de áudio</string>
     <string name="start_here_on_background">Iniciar reprodução em segundo plano</string>
-    <string name="start_here_on_popup">Iniciar reprodução num popup</string>
+    <string name="start_here_on_popup">Iniciar reprodução em popup</string>
     <string name="show_info">Mostrar informação</string>
     <string name="tab_bookmarks">Listas de reprodução favoritas</string>
     <string name="always">Sempre</string>
@@ -202,7 +202,7 @@
     <string name="controls_download_desc">Descarregar ficheiro</string>
     <string name="controls_add_to_playlist_title">Adicionar a</string>
     <string name="use_inexact_seek_title">Utilizar pesquisa rápida</string>
-    <string name="use_inexact_seek_summary">Este tipo de pesquisa e mais rápida mas reduz a precisão. Procurar por 5, 15 ou 25 segundos não funciona corretamente</string>
+    <string name="use_inexact_seek_summary">Este tipo de pesquisa é mais rápida mas reduz a precisão. Procurar por 5, 15 ou 25 segundos não funciona corretamente</string>
     <string name="thumbnail_cache_wipe_complete_notice">Cache de imagens limpa</string>
     <string name="default_content_country_title">País padrão para conteúdo</string>
     <string name="settings_category_debug_title">Depuração</string>
@@ -237,7 +237,7 @@
     <string name="popup_player">Reprodutor \'popup\'</string>
     <string name="always_ask_open_action">Perguntar sempre</string>
     <string name="preferred_player_fetcher_notification_title">A obter informação…</string>
-    <string name="preferred_player_fetcher_notification_message">A carregar o conteúdo solicitado</string>
+    <string name="preferred_player_fetcher_notification_message">A carregar conteúdo solicitado</string>
     <string name="create_playlist">Nova lista de reprodução</string>
     <string name="rename_playlist">Mudar nome</string>
     <string name="add_to_playlist">Adicionar à lista de reprodução</string>
@@ -262,13 +262,13 @@
     <string name="subscriptions_export_unsuccessful">Não foi possível exportar as subscrições</string>
     <string name="import_youtube_instructions">Importar subscrições do YouTube do Google Takeout:
 \n
-\n1. Vá para este URL: %1$s
-\n2. Faça o login quando solicitado
-\n3. Clique em \"Todos os dados incluídos\", depois em \"Desmarcar todos\", depois selecione apenas \"subscrições\" e clique em \"OK\".
-\n4. Clique em \"Próximo passo\" e depois em \"Criar exportação\".
+\n1. Aceda ao URL: %1$s
+\n2. Inicie sessão
+\n3. Clique em \"Todos os dados incluídos\", depois em \"Desmarcar todos\", selecione \"Subscrições\" e clique em \"OK\".
+\n4. Clique em \"Próximo passo\" e, depois, em \"Criar exportação\".
 \n5. Clique no botão \"Descarregar\" após aparecer
 \n6. Clique em IMPORT FILE abaixo e selecione o ficheiro .zip descarregado
-\n7. [Se a importação do .zip falhar] Extraia o ficheiro .csv (geralmente em \"YouTube e YouTube Music/subscriptions/subscriptions.csv\"), clique em IMPORT FILE abaixo e selecione o ficheiro csv extraído</string>
+\n7. [Se a importação falhar] Extraia o ficheiro .csv (geralmente em \"YouTube e YouTube Music/subscriptions/subscriptions.csv\"), clique em IMPORT FILE abaixo e selecione o ficheiro csv extraído</string>
     <string name="import_soundcloud_instructions">Importe o seu perfil SoundCloud digitando o URL ou a ID.:
 \n
 \n1. Ative o modo desktop do seu navegador web (o site não está disponível para dispositivos móveis)
@@ -297,7 +297,7 @@
     <string name="donation_encouragement">NewPipe é desenvolvido por voluntários que utilizam o seu tempo livre para nos proporcionar a melhor experiência. Retribua para ajudar os programadores a tornarem NewPipe ainda melhor.</string>
     <string name="give_back">Contribuir</string>
     <string name="privacy_policy_title">Política de privacidade do NewPipe</string>
-    <string name="privacy_policy_encouragement">O projeto NewPipe leva a sua privacidade muito a sério. Por isso, não recolhe nenhum dado sem o seu consentimento.
+    <string name="privacy_policy_encouragement">O projeto NewPipe leva a sua privacidade muito a sério. Por isso, não recolhe dados sem o seu consentimento.
 \nA polícia de privacidade do NewPipe explica, em detalhe, os tipos de dados enviados sempre que submete um relatório de erro.</string>
     <string name="read_privacy_policy">Ver política de privacidade</string>
     <string name="auto_queue_title">Adicionar o próximo vídeo à fila automaticamente</string>
@@ -309,7 +309,7 @@
     <string name="set_as_playlist_thumbnail">Definir como miniatura da lista de reprodução</string>
     <string name="resize_fit">Ajustar</string>
     <string name="resize_fill">Preencher</string>
-    <string name="caption_setting_description">Modificar escala de legendas e estilo de fundo. Tem que reiniciar a aplicação para aplicar as alterações</string>
+    <string name="caption_setting_description">Alterar escala das legendas e do estilo de fundo. Tem que reiniciar a aplicação para aplicar as alterações</string>
     <string name="enable_leak_canary_summary">A monitorização de memória pode tornar a aplicação instável</string>
     <string name="enable_disposed_exceptions_title">Reportar erros \'out-of-lifecycle\'</string>
     <string name="enable_disposed_exceptions_summary">Forçar reporte de exceções Rx não entregáveis ocorrendo fora do fragmento ou ciclo de vida da atividade após eliminação</string>
@@ -343,7 +343,7 @@
     <string name="saved_tabs_invalid_json">Não foi possível ler os separadores guardados e vamos usar os separadores padrão</string>
     <string name="restore_defaults">Repor predefinições</string>
     <string name="restore_defaults_confirmation">Deseja repor as predefinições\?</string>
-    <string name="subscribers_count_not_available">Contagem de subscrições indisponível</string>
+    <string name="subscribers_count_not_available">Número de subscrições indisponível</string>
     <string name="main_page_content_summary">Separadores mostrados na página principal</string>
     <string name="updates_setting_title">Atualizações</string>
     <string name="updates_setting_description">Mostrar uma notificação para pedir a atualização da aplicação se existir uma nova versão</string>
@@ -356,7 +356,7 @@
     <string name="paused">em pausa</string>
     <string name="queued">na fila</string>
     <string name="post_processing">pós-processamento</string>
-    <string name="enqueue">Enfileirar</string>
+    <string name="enqueue">Colocar na fila</string>
     <string name="permission_denied">Ação recusada pelo sistema</string>
     <string name="download_failed">Falha ao descarregar</string>
     <string name="generate_unique_name">Gerar nome único</string>
@@ -370,14 +370,14 @@
     <string name="error_unknown_host">Não foi possível encontrar o servidor</string>
     <string name="error_connect_host">Não foi possível ligar ao servidor</string>
     <string name="error_http_no_content">O servidor não envia dados</string>
-    <string name="error_http_unsupported_range">O servidor não aceita transferências multi-processo, tente novamente com @string/msg_threads = 1</string>
+    <string name="error_http_unsupported_range">O servidor não aceita descargas multi-processo, tente novamente com @string/msg_threads = 1</string>
     <string name="error_http_not_found">Não encontrado</string>
     <string name="error_postprocessing_failed">Falha pós-processamento</string>
     <string name="stop">Parar</string>
     <string name="max_retry_msg">Tentativas máximas</string>
     <string name="max_retry_desc">Número máximo de tentativas antes de cancelar a descarga</string>
     <string name="pause_downloads_on_mobile">Interromper em redes limitadas</string>
-    <string name="pause_downloads_on_mobile_desc">Útil ao trocar para dados móveis, mas algumas transferências não podem ser suspensas</string>
+    <string name="pause_downloads_on_mobile_desc">Útil ao trocar para dados móveis, mas algumas descargas não podem ser suspensas</string>
     <string name="events">Eventos</string>
     <string name="conferences">Conferências</string>
     <string name="missions_header_pending">Pendente</string>
@@ -395,7 +395,7 @@
     <string name="watch_history_states_deleted">Posições de reprodução removidas</string>
     <string name="missing_file">Ficheiro movido ou eliminado</string>
     <string name="overwrite_unrelated_warning">Já existe um ficheiro com este nome</string>
-    <string name="overwrite_failed">não é possível sobrescrever o ficheiro</string>
+    <string name="overwrite_failed">Não foi possível substituir o ficheiro</string>
     <string name="download_already_pending">Existe uma descarga pendente com este nome</string>
     <string name="error_postprocessing_stopped">NewPipe foi fechado enquanto trabalhava no ficheiro</string>
     <string name="error_insufficient_storage">Não há espaço livre no dispositivo</string>
@@ -404,13 +404,13 @@
     <string name="confirm_prompt">Deseja limpar o histórico de descargas ou remover todos os ficheiros descarregados\?</string>
     <string name="enable_queue_limit">Limitar fila de descargas</string>
     <string name="enable_queue_limit_desc">Permitir apenas uma descarga de cada vez</string>
-    <string name="start_downloads">Iniciar transferências</string>
-    <string name="pause_downloads">Pausa nas transferências</string>
-    <string name="downloads_storage_ask_title">Perguntar para onde transferir</string>
-    <string name="downloads_storage_ask_summary">Ser-lhe-á perguntado onde guardar cada transferência.
-\nAtive o seletor de pastas do sistema (SAF) se quiser transferir para um cartão SD externo</string>
+    <string name="start_downloads">Iniciar descargas</string>
+    <string name="pause_downloads">Pausa nas descargas</string>
+    <string name="downloads_storage_ask_title">Perguntar onde guardar</string>
+    <string name="downloads_storage_ask_summary">Ser-lhe-á perguntado onde guardar cada descarga.
+\nAtive o seletor de pastas do sistema (SAF) se quiser descarregar para um cartão SD externo</string>
     <string name="downloads_storage_use_saf_title">Utilizar seletor de pastas do sistema (SAF)</string>
-    <string name="downloads_storage_use_saf_summary">\'Storage Access Framework\' permite transferências para um cartão SD externo</string>
+    <string name="downloads_storage_use_saf_summary">\'Storage Access Framework\' permite descargas para um cartão SD externo</string>
     <string name="clear_playback_states_title">Remover posições de reprodução</string>
     <string name="clear_playback_states_summary">Remove todas as posições de reprodução</string>
     <string name="delete_playback_states_alert">Remover todas as posições de reprodução\?</string>
@@ -428,8 +428,8 @@
         <item quantity="many">%s ouvintes</item>
         <item quantity="other">%s ouvintes</item>
     </plurals>
-    <string name="localization_changes_requires_app_restart">O idioma será alterado assim que reiniciar a app</string>
-    <string name="seek_duration_title">Duração da pesquisa de avanço/recuo rápido</string>
+    <string name="localization_changes_requires_app_restart">O idioma será alterado assim que reiniciar a aplicação</string>
+    <string name="seek_duration_title">Duração de avanço/recuo rápido</string>
     <string name="peertube_instance_url_title">Instâncias PeerTube</string>
     <string name="peertube_instance_url_summary">Defina as suas instâncias PeerTube preferidas</string>
     <string name="peertube_instance_url_help">Encontre as instâncias que gosta em %s</string>
@@ -441,7 +441,7 @@
     <string name="local">Local</string>
     <string name="recently_added">Recentes</string>
     <string name="most_liked">Mais apreciados</string>
-    <string name="playlist_no_uploader">Geração automática (não foi encontrado nenhum enviador)</string>
+    <string name="playlist_no_uploader">Geração automática (não foi encontrado nenhum carregador)</string>
     <string name="recovering">a recuperar</string>
     <string name="error_download_resource_gone">Não é possível recuperar esta descarga</string>
     <string name="choose_instance_prompt">Escolha uma instância</string>
@@ -451,11 +451,11 @@
     <string name="app_language_title">Idioma da aplicação</string>
     <string name="systems_language">Predefinição do sistema</string>
     <string name="subtitle_activity_recaptcha">Prima \"Feito\" ao resolver</string>
-    <string name="done">Concluído</string>
-    <string name="feed_use_dedicated_fetch_method_help_text">Acha que a fonte demora muito tempo a carregar\? Se sim, tente ativar o carregamento rápido (pode alterar a opção nas definições ou no botão abaixo).
+    <string name="done">Feito</string>
+    <string name="feed_use_dedicated_fetch_method_help_text">Acha que a fonte demora muito tempo a carregar? Se sim, tente ativar o carregamento rápido (pode alterar a opção nas definições ou no botão abaixo).
 \n
 \nNewPipe oferece duas estratégias de carregamento:
-\n- Obter todo o canal de subscrição - lento, mas completo.
+\n- Obter todo o canal - lento, mas completo.
 \n- Usar um \'endpoint\' dedicado - mais rápido, mas não completo.
 \n
 \nA diferença entre os dois é que o rápido geralmente carece de alguma informação, como a duração ou tipo do item (não consegue distinguir entre vídeos em direto e vídeos normais) e pode mostrar menos itens.
@@ -468,11 +468,11 @@
     <string name="feed_use_dedicated_fetch_method_summary">Disponível em alguns serviços, é geralmente muito mais rápido, mas pode devolver uma quantidade limitada de itens e muitas vezes informações incompletas (por exemplo, sem duração, tipo de item, sem estado ativo)</string>
     <string name="feed_use_dedicated_fetch_method_title">Se possível, obter de uma fonte dedicada</string>
     <string name="feed_update_threshold_option_always_update">Atualizar sempre</string>
-    <string name="feed_update_threshold_summary">Tempo após a última atualização antes de a subscrição ser considerada desatualizada - %s</string>
+    <string name="feed_update_threshold_summary">Tempo após a última atualização antes da subscrição ser considerada desatualizada - %s</string>
     <string name="feed_update_threshold_title">Limite de atualização da fonte</string>
     <string name="settings_category_feed_title">Fonte</string>
     <string name="feed_create_new_group_button_title">Novo</string>
-    <string name="feed_group_dialog_delete_message">Deseja apagar este grupo\?</string>
+    <string name="feed_group_dialog_delete_message">Deseja remover este grupo?</string>
     <string name="feed_group_dialog_empty_name">O nome do grupo está vazio</string>
     <plurals name="feed_group_dialog_selection_count">
         <item quantity="one">%d selecionado</item>
@@ -547,18 +547,18 @@
     <string name="notification_scale_to_square_image_summary">Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1</string>
     <string name="notification_scale_to_square_image_title">Cortar miniatura na proporção 1:1</string>
     <string name="autoplay_summary">Iniciar reprodução automaticamente — %s</string>
-    <string name="title_activity_play_queue">Reproduzir fila</string>
+    <string name="title_activity_play_queue">Fila de reprodução</string>
     <string name="never">Nunca</string>
     <string name="notification_action_buffering">A carregar</string>
-    <string name="clear_queue_confirmation_description">A fila do reprodutor ativo será substituída</string>
+    <string name="clear_queue_confirmation_description">A fila de reprodução atual será substituída</string>
     <string name="unsupported_url_dialog_message">URL não reconhecido. Abrir com outra aplicação\?</string>
-    <string name="auto_queue_toggle">Enfileiramento automático</string>
+    <string name="auto_queue_toggle">Colocar na fila automaticamente</string>
     <string name="notification_action_shuffle">Baralhar</string>
     <string name="wifi_only">Apenas em Wi-Fi</string>
     <string name="notification_action_nothing">Nada</string>
     <string name="clear_queue_confirmation_summary">Mudar de um reprodutor para outro pode substituir a sua fila</string>
     <string name="clear_queue_confirmation_title">Pedir confirmação antes de limpar a fila</string>
-    <string name="notification_actions_summary">Edite cada ação de notificação abaixo com um toque. Selecione até três para serem mostradas na notificação compacta utilizando as caixas de seleção à direita</string>
+    <string name="notification_actions_summary">Edite cada ação de notificação abaixo, tocando na mesma. Seleccione até três delas para serem mostradas na notificação compacta, utilizando as caixas de verificação à direita.</string>
     <string name="notification_actions_at_most_three">Pode selecionar, no máximo, três ações para mostrar na notificação compacta!</string>
     <string name="notification_action_repeat">Repetir</string>
     <string name="notification_action_4_title">Quinto botão de ação</string>
@@ -572,7 +572,7 @@
     <string name="show_age_restricted_content_summary">Mostrar conteúdo possivelmente impróprio para crianças porque tem um limite de idade (como 18+)</string>
     <string name="notification_colorize_summary">Personalizar cor da notificação com a cor principal da miniatura (esta opção não está disponível em todos os dispositivos)</string>
     <string name="notification_colorize_title">Notificação colorida</string>
-    <string name="show_thumbnail_summary">Usar miniaturas no fundo do ecrã de bloqueio e em notificações</string>
+    <string name="show_thumbnail_summary">Usar miniatura como fundo do ecrã de bloqueio e nas notificações</string>
     <string name="show_thumbnail_title">Mostrar miniatura</string>
     <string name="msg_calculating_hash">A calcular \'hash\'</string>
     <string name="hash_channel_description">Notificar sobre o progresso das \'hash\' dos vídeos</string>
@@ -583,18 +583,18 @@
     <string name="no_app_to_open_intent">Não possui qualquer aplicação para abrir este ficheiro</string>
     <string name="chapters">Capítulos</string>
     <string name="description_tab_description">Descrição</string>
-    <string name="related_items_tab_description">Emissões relacionadas</string>
+    <string name="related_items_tab_description">Itens relacionados</string>
     <string name="comments_tab_description">Comentários</string>
     <string name="show_description_summary">Desative para ocultar a descrição do vídeo e informações adicionais</string>
     <string name="show_description_title">Mostrar descrição</string>
     <string name="open_with">Abrir com</string>
-    <string name="crash_the_app">A app travou</string>
+    <string name="crash_the_app">Terminar aplicação</string>
     <string name="restricted_video_no_stream">Este vídeo tem uma restrição de idade.
 \nDevido às novas políticas do YouTube quanto a restrição de alguns vídeos, o NewPipe não pode aceder a estes vídeos e, por isso, não os consegue reproduzir.</string>
-    <string name="paid_content">Este conteúdo só está disponível para utilizadores que pagaram, portanto não pode ser transmitido ou descarregado pelo NewPipe.</string>
-    <string name="youtube_music_premium_content">Este vídeo está disponível apenas para os membros do YouTube Music Premium, portanto não pode ser transmitido ou descarregado pelo NewPipe.</string>
-    <string name="private_content">Este conteúdo é privado, portanto não pode ser transmitido ou descarregado pelo NewPipe.</string>
-    <string name="soundcloud_go_plus_content">Esta é uma faixa de SoundCloud Go+, pelo menos no seu país, pelo que não pode ser transmitida ou descarregada por NewPipe.</string>
+    <string name="paid_content">Este conteúdo só está disponível para utilizadores pagantes e não pode ser reproduzido ou descarregado pelo NewPipe.</string>
+    <string name="youtube_music_premium_content">Este vídeo só está disponível para os membros do YouTube Music Premium e não pode ser reproduzido ou descarregado pelo NewPipe.</string>
+    <string name="private_content">Este conteúdo é privado e não pode ser reproduzido ou descarregado pelo NewPipe.</string>
+    <string name="soundcloud_go_plus_content">Esta é uma faixa SoundCloud Go+ e, pelo menos no seu país, não pode ser reproduzida ou descarregada por NewPipe.</string>
     <string name="georestricted_content">Este conteúdo não está disponível no seu país.</string>
     <string name="radio">Rádio</string>
     <string name="featured">Destaques</string>
@@ -605,37 +605,37 @@
     <string name="auto_device_theme_title">Automático (Tema do dispositivo)</string>
     <string name="night_theme_title">Tema escuro</string>
     <string name="show_channel_details">Mostrar detalhes do canal</string>
-    <string name="disable_media_tunneling_summary">Desative o túnel multimédia se tiver um ecrã preto ou gaguejo na reprodução de vídeo.</string>
+    <string name="disable_media_tunneling_summary">Desative o túnel multimédia se tiver um ecrã preto ou paragens na reprodução de vídeos.</string>
     <string name="disable_media_tunneling_title">Desativar túnel multimédia</string>
     <string name="downloads_storage_ask_summary_no_saf_notice">Sempre que descarregar um ficheiro, terá que indicar o local para o guardar</string>
-    <string name="no_dir_yet">Ainda não foi definida uma pasta de descarregamento, escolha agora a pasta de descarregamento padrão</string>
+    <string name="no_dir_yet">Ainda não definiu uma pasta para as descargas. Escolha agora a pasta a utilizar</string>
     <string name="service_provides_reason">%s fornece este motivo:</string>
     <string name="account_terminated">Conta encerrada</string>
-    <string name="feed_load_error_fast_unknown">O modo de feed rápido não fornece mais informações sobre isto.</string>
+    <string name="feed_load_error_fast_unknown">O modo de fonte rápida não fornece mais informações sobre isto.</string>
     <string name="feed_load_error_terminated">A conta do autor foi encerrada.
-\nNewPipe não será capaz de carregar este feed no futuro.
-\nQuer cancelar a inscrição deste canal\?</string>
-    <string name="feed_load_error_account_info">Não foi possível carregar o feed para \'%s\'.</string>
-    <string name="feed_load_error">Erro ao carregar o feed</string>
-    <string name="downloads_storage_use_saf_summary_api_29">A partir do Android 10, apenas o \'Storage Access Framework\' é compatível</string>
-    <string name="seekbar_preview_thumbnail_title">Pré-visualização da miniatura da barra de pesquisa</string>
+\nNewPipe não será capaz de carregar esta fonte.
+\nDeseja cancelar a subscrição deste canal?</string>
+    <string name="feed_load_error_account_info">Não foi possível carregar a fonte para \'%s\'.</string>
+    <string name="feed_load_error">Erro ao carregar a fonte</string>
+    <string name="downloads_storage_use_saf_summary_api_29">A partir de Android 10, apenas \'Storage Access Framework\' é compatível</string>
+    <string name="seekbar_preview_thumbnail_title">Pré-visualização da miniatura na barra de pesquisa</string>
     <string name="mark_as_watched">Marcar como visto</string>
     <string name="off">Desligado</string>
     <string name="on">Ligado</string>
     <string name="tablet_mode_title">Modo tablet</string>
-    <string name="open_website_license">Abrir site de web</string>
-    <string name="detail_heart_img_view_description">Entusiasmado pelo criador</string>
+    <string name="open_website_license">Abrir site</string>
+    <string name="detail_heart_img_view_description">Adorado pelo criador</string>
     <string name="metadata_privacy_internal">Interno</string>
     <string name="metadata_privacy_private">Privado</string>
     <string name="metadata_privacy_unlisted">Não listado</string>
     <string name="metadata_privacy_public">Público</string>
     <string name="metadata_host">Servidor</string>
-    <string name="metadata_support">Apoio</string>
+    <string name="metadata_support">Suporte</string>
     <string name="metadata_language">Idioma</string>
     <string name="metadata_age_limit">Limite de idade</string>
     <string name="metadata_privacy">Privacidade</string>
     <string name="metadata_licence">Licença</string>
-    <string name="metadata_tags">Marcadores</string>
+    <string name="metadata_tags">Etiquetas</string>
     <string name="metadata_category">Categoria</string>
     <string name="description_select_disable">Desativar seleção de texto na descrição</string>
     <string name="description_select_enable">Ativar seleção de texto na descrição</string>
@@ -643,7 +643,7 @@
     <string name="dont_show">Não mostrar</string>
     <string name="low_quality_smaller">Baixa qualidade (menor)</string>
     <string name="high_quality_larger">Alta qualidade (maior)</string>
-    <string name="comments_are_disabled">Comentários estão desativados</string>
+    <string name="comments_are_disabled">Os comentários estão desativados</string>
     <string name="show_image_indicators_summary">Mostrar fitas coloridas de Picasso em cima das imagens que indicam a sua fonte: vermelho para rede, azul para disco e verde para memória</string>
     <string name="show_image_indicators_title">Mostrar indicadores de imagem</string>
     <string name="remote_search_suggestions">Sugestões de pesquisa remotas</string>
@@ -658,112 +658,112 @@
         <item quantity="many">%s descargas concluídas</item>
         <item quantity="other">%s descargas concluídas</item>
     </plurals>
-    <string name="main_page_content_swipe_remove">Deslizar itens para removê-los</string>
+    <string name="main_page_content_swipe_remove">Deslize nos itens para os remover</string>
     <string name="start_main_player_fullscreen_summary">Não iniciar vídeos em mini-reprodutor e ativar ecrã completo, se a rotação automática estiver bloqueada. Pode aceder ao mini-reprodutor se sair do modo de ecrã completo.</string>
     <string name="start_main_player_fullscreen_title">Iniciar reprodutor principal em ecrã completo</string>
-    <string name="enqueued_next">Enfileirado o próximo</string>
-    <string name="enqueue_next_stream">Pôr na fila o próximo</string>
-    <string name="processing_may_take_a_moment">A processar… Pode demorar um momento</string>
+    <string name="enqueued_next">Seguinte colocado na fila</string>
+    <string name="enqueue_next_stream">Colocar seguinte na fila</string>
+    <string name="processing_may_take_a_moment">A processar… Pode levar algum tempo</string>
     <string name="manual_update_title">Procurar atualizações</string>
     <string name="manual_update_description">Verificar manualmente se existe uma nova versão</string>
     <string name="checking_updates_toast">A procurar atualizações…</string>
     <string name="feed_new_items">Novos itens</string>
-    <string name="crash_the_player">Travar o reprodutor</string>
+    <string name="crash_the_player">Terminou o reprodutor</string>
     <string name="show_crash_the_player_title">Mostrar \"Travar o reprodutor\"</string>
-    <string name="show_crash_the_player_summary">Mostra uma opção de travamento ao usar o reprodutor</string>
+    <string name="show_crash_the_player_summary">Mostra uma opção para terminar o reprodutor</string>
     <string name="error_report_channel_name">Notificação para relatórios de erro</string>
     <string name="error_report_channel_description">Notificações para reportar erros</string>
     <string name="error_report_notification_title">NewPipe encontrou um erro, toque para reportar</string>
     <string name="error_report_notification_toast">Ocorreu um erro, veja a notificação</string>
-    <string name="show_error_snackbar">Mostrar um snackbar de erro</string>
+    <string name="show_error_snackbar">Mostrar uma barra de erros</string>
     <string name="create_error_notification">Criar uma notificação de erro</string>
-    <string name="no_appropriate_file_manager_message">Nenhum gestor de ficheiros apropriado foi encontrado para esta ação.
-\nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar</string>
+    <string name="no_appropriate_file_manager_message">Não foi encontrado um gestor de ficheiros apropriado para esta ação.
+\nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas definições das descargas</string>
     <string name="no_appropriate_file_manager_message_android_10">Nenhum gestor de ficheiros apropriado foi encontrado para esta ação.
-\nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework</string>
-    <string name="detail_pinned_comment_view_description">Comentário fixado</string>
-    <string name="leak_canary_not_available">LeakCanary não está disponível</string>
-    <string name="progressive_load_interval_exoplayer_default">Predefinido do ExoPlayer</string>
+\nPor favor, instale um gestor de ficheiros compatível com SAF (Storage Access Framework)</string>
+    <string name="detail_pinned_comment_view_description">Comentário afixado</string>
+    <string name="leak_canary_not_available">LeakCanary não disponível</string>
+    <string name="progressive_load_interval_exoplayer_default">Predefinição ExoPlayer</string>
     <string name="settings_category_player_notification_title">Notificação do reprodutor</string>
     <string name="settings_category_player_notification_summary">Configurar notificação da reprodução do vídeo atual</string>
     <string name="notifications">Notificações</string>
     <string name="loading_stream_details">A carregar detalhes do vídeo…</string>
-    <string name="check_new_streams">Verificar se há novos vídeos</string>
+    <string name="check_new_streams">Procurar novos vídeos</string>
     <string name="enable_streams_notifications_title">Notificações sobre novos vídeos</string>
-    <string name="enable_streams_notifications_summary">Notificar sobre novos vídeos nas assinaturas</string>
+    <string name="enable_streams_notifications_summary">Notificar sobre novos vídeos nas subscrições</string>
     <string name="streams_notifications_interval_title">Frequência da verificação</string>
-    <string name="streams_notifications_network_title">Conexão de rede necessária</string>
+    <string name="streams_notifications_network_title">Requer uma ligação de rede</string>
     <string name="any_network">Qualquer rede</string>
-    <string name="you_successfully_subscribed">Agora assinou este canal</string>
+    <string name="you_successfully_subscribed">É agora um subscritor deste canal</string>
     <string name="toggle_all">Alternar tudo</string>
-    <string name="delete_downloaded_files_confirm">Apagar todos os ficheiros descarregados do disco\?</string>
+    <string name="delete_downloaded_files_confirm">Remover todos os ficheiros descarregados?</string>
     <string name="streams_notification_channel_name">Novos vídeos</string>
     <string name="streams_notification_channel_description">Notificações sobre novos vídeos nas subscrições</string>
     <plurals name="new_streams">
-        <item quantity="one">%s vídeo novo</item>
-        <item quantity="many">%s vídeos novos</item>
-        <item quantity="other">%s vídeos novos</item>
+        <item quantity="one">%s novo vídeo</item>
+        <item quantity="many">%s novos vídeos</item>
+        <item quantity="other">%s novos vídeos</item>
     </plurals>
-    <string name="get_notified">Seja notificado</string>
-    <string name="notifications_disabled">Notificações são desativadas</string>
+    <string name="get_notified">Obter notificação</string>
+    <string name="notifications_disabled">As notificações estão desativadas</string>
     <string name="enumeration_comma">,</string>
-    <string name="percent">Por cento</string>
-    <string name="semitone">Semitom</string>
-    <string name="streams_not_yet_supported_removed">As transmissões que ainda não são suportadas para descarregamento não são mostradas</string>
-    <string name="no_audio_streams_available_for_external_players">Não estão disponíveis transmissões de áudio a reprodutores externos</string>
-    <string name="no_video_streams_available_for_external_players">Não estão disponíveis transmissões de vídeo a reprodutores externos</string>
+    <string name="percent">Percentual</string>
+    <string name="semitone">Semi-tom</string>
+    <string name="streams_not_yet_supported_removed">Os vídeos não suportadas para descarga não são mostradas</string>
+    <string name="no_audio_streams_available_for_external_players">Não estão disponíveis reproduções de áudio para reprodutores externos</string>
+    <string name="no_video_streams_available_for_external_players">Não estão disponíveis reproduções de vídeo para reprodutores externos</string>
     <string name="select_quality_external_players">Selecione a qualidade para reprodutores externos</string>
     <string name="unknown_format">Formato desconhecido</string>
     <string name="unknown_quality">Qualidade desconhecida</string>
-    <string name="selected_stream_external_player_not_supported">A transmissão selecionada não é suportada por reprodutores externos</string>
+    <string name="selected_stream_external_player_not_supported">O vídeo selecionado não é suportado por reprodutores externos</string>
     <string name="progressive_load_interval_title">Tamanho do intervalo de carregamento da reprodução</string>
     <string name="faq_title">Perguntas frequentes</string>
     <string name="faq">Ver no site</string>
-    <string name="faq_description">Se está a ter problemas a usar a aplicação, veja estas respostas para perguntas mais comuns!</string>
-    <string name="sort">Ordenação</string>
+    <string name="faq_description">Se está a ter problemas a usar a aplicação, veja estas respostas para as perguntas mais comuns!</string>
+    <string name="sort">Ordem</string>
     <string name="fast_mode">Modo rápido</string>
     <string name="import_subscriptions_hint">Importe ou exporte subscrições no menu 3 pontos</string>
     <string name="app_update_unavailable_toast">Já está a executar a versão mais recente do NewPipe</string>
     <string name="app_update_available_notification_text">Toque para descarregar %s</string>
     <string name="night_theme_available">Esta opção só está disponível se estiver selecionado %s para o tema</string>
-    <string name="unset_playlist_thumbnail">Desativar miniatura permanente</string>
+    <string name="unset_playlist_thumbnail">Desativar miniatura permanentemente</string>
     <string name="msg_failed_to_copy">Não foi possível copiar para a área de transferência</string>
     <string name="card">Cartão</string>
     <string name="duplicate_in_playlist">As listas de reprodução acinzentadas já contêm este item.</string>
-    <string name="playlist_add_stream_success_duplicate">Duplicar adicionado %d vez(es)</string>
-    <string name="ignore_hardware_media_buttons_title">Ignorar eventos com botões físicos</string>
+    <string name="playlist_add_stream_success_duplicate">Duplicado adicionado %d vez(es)</string>
+    <string name="ignore_hardware_media_buttons_title">Ignorar botões físicos</string>
     <string name="ignore_hardware_media_buttons_summary">Útil, por exemplo, se estiver a utilizar auscultadores com botões físicos danificados</string>
     <string name="remove_duplicates">Remover duplicados</string>
     <string name="remove_duplicates_title">Remover duplicados\?</string>
     <string name="feed_show_hide_streams">Mostrar/ocultar vídeos</string>
-    <string name="feed_show_upcoming">Proximamente</string>
-    <string name="remove_duplicates_message">Quer remover todos os vídeos duplicados nesta lista de reprodução\?</string>
+    <string name="feed_show_upcoming">Brevemente</string>
+    <string name="remove_duplicates_message">Deseja remover todos os vídeos duplicados desta lista de reprodução?</string>
     <string name="feed_show_watched">Completamente visto</string>
-    <string name="feed_hide_streams_title">Mostrar os seguintes vídeos</string>
+    <string name="feed_hide_streams_title">Mostrar vídeos seguintes</string>
     <string name="feed_show_partially_watched">Parcialmente visto</string>
-    <string name="left_gesture_control_title">Ação do gesto para a esquerda</string>
-    <string name="right_gesture_control_title">Ação do gesto para a direita</string>
+    <string name="left_gesture_control_title">Ação para o gesto à esquerda</string>
+    <string name="right_gesture_control_title">Ação para o gesto à direita</string>
     <string name="brightness">Brilho</string>
     <string name="volume">Volume</string>
-    <string name="none">Nenhum</string>
-    <string name="left_gesture_control_summary">Escolha o gesto para a metade esquerda do ecrã do reprodutor</string>
-    <string name="right_gesture_control_summary">Escolha o gesto para a metade direita do ecrã do reprodutor</string>
+    <string name="none">Nada</string>
+    <string name="left_gesture_control_summary">Escolha o gesto para a parte esquerda do ecrã</string>
+    <string name="right_gesture_control_summary">Escolha o gesto para a parte direita do ecrã</string>
     <string name="prefer_descriptive_audio_summary">Seleciona o áudio com descrição para pessoas com dificuldades de visão, se disponível</string>
-    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solução alternativa liberta e re-instancia os codificadores de vídeo quando ocorre uma alteração da superfície, em vez de definir diretamente a superfície para o codificador. Já utilizada pelo ExoPlayer em alguns dispositivos com este problema, esta configuração só tem efeito no Android 6 e superior
+    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solução alternativa liberta os codificadores de vídeo quando ocorre uma alteração da superfície, em vez de definir diretamente a superfície para o codificador. Já utilizada pelo ExoPlayer em alguns dispositivos com este problema, esta definição só tem efeito no Android 6 e superior
 \n
-\nA activação desta opção pode evitar erros de reprodução ao mudar o leitor de vídeo atual ou ao mudar para ecrã inteiro</string>
-    <string name="progressive_load_interval_summary">Altera o tamanho do intervalo de carregamento progressivo (o atual é %s). Um valor menor pode acelerar o carregamento inicial do vídeo</string>
+\nA ativação desta opção pode evitar erros de reprodução ao mudar o leitor de vídeo atual ou ao mudar para ecrã inteiro</string>
+    <string name="progressive_load_interval_summary">Altera o tamanho do intervalo de carregamento progressivo (atualmente %s). Um valor menor pode acelerar o carregamento inicial do conteúdo</string>
     <string name="prefer_original_audio_title">Dar preferência ao áudio original</string>
     <string name="prefer_original_audio_summary">Seleciona o áudio original, independentemente do idioma</string>
     <string name="prefer_descriptive_audio_title">Dar preferência ao áudio descritivo</string>
     <string name="play_queue_audio_track">Áudio: %s</string>
     <string name="audio_track">Faixa de áudio</string>
-    <string name="audio_track_present_in_video">Já deve existir uma faixa de áudio nesta transmissão</string>
-    <string name="select_audio_track_external_players">Selecionar faixa de áudio para reprodutores externos</string>
+    <string name="audio_track_present_in_video">Já deve existir uma faixa de áudio neste vídeo</string>
+    <string name="select_audio_track_external_players">Selecione a faixa de áudio para reprodutores externos</string>
     <string name="unknown_audio_track">Desconhecida</string>
-    <string name="settings_category_exoplayer_title">Configurações ExoPlayer</string>
-    <string name="settings_category_exoplayer_summary">Gere algumas configurações de ExoPlayer. É necessário reiniciar o reprodutor para aplicar as alterações</string>
-    <string name="use_exoplayer_decoder_fallback_title">Utilizar a função de fallback do descodificador do ExoPlayer</string>
+    <string name="settings_category_exoplayer_title">Definições ExoPlayer</string>
+    <string name="settings_category_exoplayer_summary">Gere algumas definições ExoPlayer. Tem que reiniciar o reprodutor para aplicar as alterações</string>
+    <string name="use_exoplayer_decoder_fallback_title">Utilizar descodificador se recurso do ExoPlayer</string>
     <string name="use_exoplayer_decoder_fallback_summary">Ative esta opção se tiver problemas de inicialização do descodificador, que retorna codificadores de baixa prioridade se o descodificador primário falhar. Isto pode resultar num desempenho inferior de reprodução</string>
     <string name="always_use_exoplayer_set_output_surface_workaround_title">Usar sempre a configuração de saída alternativa de vídeo do ExoPlayer</string>
     <string name="audio_track_name">%1$s %2$s</string>
@@ -772,12 +772,12 @@
     <string name="audio_track_type_descriptive">descritivo</string>
     <string name="main_tabs_position_summary">Mova o seletor do separador principal para a parte inferior</string>
     <string name="main_tabs_position_title">Posição dos separadores principais</string>
-    <string name="disable_media_tunneling_automatic_info">O túnel multimédia foi desativado por predefinição no seu dispositivo porque se sabe que o modelo do dispositivo não o suporta.</string>
+    <string name="disable_media_tunneling_automatic_info">O túnel multimédia foi desativado no seu dispositivo porque o seu dispositivo não tem suporte à opção.</string>
     <string name="no_live_streams">Nenhum vídeo em direto</string>
     <string name="no_streams">Nenhum vídeo</string>
     <string name="loading_metadata_title">A carregar metadados…</string>
     <string name="feed_fetch_channel_tabs">Obter separadores de canais</string>
-    <string name="feed_fetch_channel_tabs_summary">Separadores a obter ao atualizar o feed. Esta opção não tem efeito se um canal for atualizado utilizando o modo rápido.</string>
+    <string name="feed_fetch_channel_tabs_summary">Separadores a obter ao atualizar a fonte. Esta opção não tem efeito se um canal for atualizado utilizando o modo rápido.</string>
     <string name="image_quality_title">Qualidade da imagem</string>
     <string name="channel_tab_videos">Vídeos</string>
     <string name="question_mark">\?</string>
@@ -793,7 +793,7 @@
     <string name="image_quality_low">Baixa qualidade</string>
     <string name="toggle_fullscreen">Alternar ecrã completo</string>
     <string name="metadata_avatars">Avatars</string>
-    <string name="next_stream">Fluxo seguinte</string>
+    <string name="next_stream">Vídeo seguinte</string>
     <string name="metadata_subchannel_avatars">Avatar de subcanais</string>
     <string name="open_play_queue">Abrir fila de reprodução</string>
     <string name="image_quality_none">Não carregar imagens</string>
@@ -815,8 +815,16 @@
     <string name="channel_tab_tracks">Faixas</string>
     <string name="duration">Duração</string>
     <string name="channel_tab_channels">Canais</string>
-    <string name="previous_stream">Fluxo anterior</string>
+    <string name="previous_stream">Vídeo anterior</string>
     <string name="channel_tab_livestreams">Direto</string>
     <string name="share_playlist_with_titles_message">Partilhe a lista de reprodução com detalhes como o nome da lista de reprodução e os títulos dos vídeos ou como uma simples lista de URLs de vídeos</string>
     <string name="image_quality_summary">Escolha a qualidade das imagens e se pretende carregar imagens, para reduzir a utilização de dados e de memória. As alterações limpam a cache de imagens na memória e no disco - %s</string>
+    <string name="show_more">Mostrar mais</string>
+    <plurals name="replies">
+        <item quantity="one">%s resposta</item>
+        <item quantity="many">%s respostas</item>
+        <item quantity="other">%s respostas</item>
+    </plurals>
+    <string name="show_less">Mostrar menos</string>
+    <string name="notification_actions_summary_android13">Edite cada ação de notificação abaixo a tocar nela. As três primeiras ações (reproduzir/pausa, anterior e seguinte) são definidas pelo sistema e não podem ser personalizadas.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 42e23c3a4..bc3b81e1e 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -176,7 +176,7 @@
     <string name="no_player_found_toast">Niciun player pentru streaming găsit. (Totuși, puteți instala VLC).</string>
     <string name="controls_download_desc">Descărcați fișierul de flux</string>
     <string name="show_info">Afișare informații</string>
-    <string name="tab_bookmarks">Playlist-uri salvate</string>
+    <string name="tab_bookmarks">Playlisturi salvate</string>
     <string name="controls_add_to_playlist_title">Salvare în</string>
     <string name="use_inexact_seek_title">Folosește parcurgerea rapidă inexactă</string>
     <string name="use_inexact_seek_summary">Derularea inexactă permite player-ului să deruleze mai rapid, cu o precizie redusă. Derularea timp de 5, 15 sau 25 de secunde nu funcționează cu aceasta</string>
@@ -363,7 +363,7 @@
     <string name="notification_action_shuffle">Redare aleatorie</string>
     <string name="notification_action_repeat">Repetare</string>
     <string name="notification_actions_at_most_three">Puteți selecta cel mult trei acțiuni pentru afișare în notificarea compactă!</string>
-    <string name="notification_actions_summary">Modificați fiecare acțiune de notificare de mai jos, atingând-o. Selectați până la trei dintre ele pentru a fi afișate în notificarea compactă, utilizând casetele de selectare din dreapta</string>
+    <string name="notification_actions_summary">Modifică fiecare acțiune de notificare de mai jos, atingând-o. Selectează până la trei dintre ele pentru a fi afișate în notificarea compactă, utilizând casetele de selectare din dreapta.</string>
     <string name="notification_action_4_title">Al cincilea buton de acțiune</string>
     <string name="notification_action_3_title">Al patrulea buton de acțiune</string>
     <string name="notification_action_2_title">Al treilea buton de acțiune</string>
@@ -604,7 +604,7 @@
     <string name="soundcloud_go_plus_content">Aceasta este o piesă SoundCloud Go+, cel puțin în țara dvs., deci nu poate fi difuzată sau descărcată de NewPipe.</string>
     <string name="georestricted_content">Acest conținut nu este disponibil în țara dumneavoastră.</string>
     <string name="show_channel_details">Afișați detaliile canalului</string>
-    <string name="night_theme_title">Temă Nocturnă</string>
+    <string name="night_theme_title">Tema de noapte</string>
     <string name="enqueued_next">Următorul pus în coadă</string>
     <string name="enqueue_next_stream">Adăugați în coadă pe următorul</string>
     <string name="mark_as_watched">Marcare ca vizionat</string>
@@ -627,7 +627,7 @@
     <string name="metadata_category">Categorie</string>
     <string name="description_select_disable">Dezactivați selectarea textului în descriere</string>
     <string name="description_select_enable">Activați selectarea textului în descriere</string>
-    <string name="description_select_note">Acum puteți selecta text în interiorul descrierii. Rețineți că este posibil ca pagina să pâlpâie, iar link-urile să nu poată fi accesate în modul de selecție.</string>
+    <string name="description_select_note">Acum puteți selecta text în interiorul descrierii. Rețineți că este posibil ca pagina să pâlpâie, iar linkurile să nu poată fi accesate în modul de selecție.</string>
     <string name="service_provides_reason">%s oferă acest motiv:</string>
     <string name="account_terminated">Contul a fost închis</string>
     <string name="feed_load_error_fast_unknown">Modul rapid nu furnizează mai multe informații în acest sens.</string>
@@ -817,4 +817,15 @@
     <string name="previous_stream">Transmisia anterioară</string>
     <string name="left_gesture_control_summary">Alegeți un gest pentru jumătatea stângă din ecranul playerului</string>
     <string name="channel_tab_livestreams">În direct</string>
+    <string name="channel_tab_shorts">Shorturi</string>
+    <string name="metadata_uploader_avatars">Avatarele autorului</string>
+    <string name="notification_actions_summary_android13">Editează fiecare acțiune de notificare de mai jos atingând-o. Primele trei acțiuni (redare/pauză, anterioară și următoare) sunt setate de sistem și nu pot fi personalizate.</string>
+    <plurals name="replies">
+        <item quantity="one">%s răspuns</item>
+        <item quantity="few">%s răspunsuri</item>
+        <item quantity="other">%s răspunsuri</item>
+    </plurals>
+    <string name="show_more">Arată mai multe</string>
+    <string name="show_less">Arată mai puține</string>
+    <string name="channel_tab_tracks">Piste</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 63d576594..32454ae72 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -78,7 +78,7 @@
     <string name="audio">Аудио</string>
     <string name="retry">Повтор</string>
     <string name="could_not_load_thumbnails">Не удалось загрузить все миниатюры</string>
-    <string name="controls_background_title">В фоне</string>
+    <string name="controls_background_title">Фон</string>
     <string name="controls_popup_title">В окне</string>
     <string name="show_higher_resolutions_summary">Только некоторые устройства поддерживают видео в 2K/4K</string>
     <string name="default_video_format_title">Формат видео по умолчанию</string>
@@ -562,7 +562,7 @@
     <string name="notification_action_shuffle">Перемешать</string>
     <string name="notification_action_repeat">Повтор</string>
     <string name="notification_actions_at_most_three">В компактном уведомлении доступно не более трёх действий!</string>
-    <string name="notification_actions_summary">Действия можно изменить, нажав на них. Отметьте не более трёх для отображения в компактном уведомлении</string>
+    <string name="notification_actions_summary">Отредактируйте каждое действие уведомления ниже, нажав на него. Выберите до трёх из них, которые будут отображаться в компактном уведомлении, используя флажки справа.</string>
     <string name="notification_action_4_title">Пятое действие</string>
     <string name="notification_action_3_title">Четвёртое действие</string>
     <string name="notification_action_2_title">Третье действие</string>
@@ -789,7 +789,7 @@
     <string name="share_playlist_content_details">%1$s
 \n%2$s</string>
     <string name="show_channel_tabs">Вкладки канала</string>
-    <string name="channel_tab_shorts">Shorts</string>
+    <string name="channel_tab_shorts">Шорты</string>
     <string name="loading_metadata_title">Загрузка метаданных…</string>
     <string name="toggle_screen_orientation">Изменить ориентацию экрана</string>
     <string name="image_quality_low">Низкое качество</string>
@@ -823,4 +823,13 @@
     <string name="channel_tab_channels">Каналы</string>
     <string name="previous_stream">Предыдущий стрим</string>
     <string name="channel_tab_livestreams">Живая трансляция</string>
+    <string name="show_more">Показать больше</string>
+    <string name="show_less">Показать меньше</string>
+    <plurals name="replies">
+        <item quantity="one">%s ответ</item>
+        <item quantity="few">%s ответа</item>
+        <item quantity="many">%s ответов</item>
+        <item quantity="other">%s ответов</item>
+    </plurals>
+    <string name="notification_actions_summary_android13">Отредактируйте каждое действие уведомления ниже, нажав на него. Первые три действия (воспроизведение/пауза, предыдущее и следующее) задаются системой и не подлежат настройке.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml
index abd1f089b..dab5815db 100644
--- a/app/src/main/res/values-ryu/strings.xml
+++ b/app/src/main/res/values-ryu/strings.xml
@@ -318,7 +318,7 @@
     <string name="import_soundcloud_instructions_hint">うんじゅがID, soundcloud.com/うんじゅがid</string>
     <string name="import_network_expensive_warning">くぬあしっさにゆりちうしんりょうきんがかじゅーいるくとぅがあいびーん。ぐちゅういください。
 \n
-\nずっこうさびーが\?</string>
+\nずっこうさびーが?</string>
     <string name="playback_speed_control">とうるくさゃすう</string>
     <string name="unhook_checkbox">すくどぅとぅうんていりんちゃーさじんかいへいるかん(ゆがむんかむしりやびらん)</string>
     <string name="skip_silence_checkbox">むうんぬあいだにはやうくり</string>
@@ -330,7 +330,7 @@
     <string name="app_update_notification_channel_description">みーさるNewPipeバージョンぬちうち</string>
     <string name="download_to_sdcard_error_title">がいぶちうちゅるすうちはりようなやびらん</string>
     <string name="restore_defaults">っちいちんかいむどぅすん</string>
-    <string name="restore_defaults_confirmation">きていぬしっていふちゅるぎんさびーが\?</string>
+    <string name="restore_defaults_confirmation">きていぬしっていふちゅるぎんさびーが?</string>
     <string name="subscribers_count_not_available">とうるくさゃすうはりようでぃきましん</string>
     <string name="conferences">がいぎ</string>
     <string name="start_accept_privacy_policy">ヨーロッパぬいちぱんデータふぐきせい(GDPR)にじゅんきょしーんたみに、NewPipeぬくじんじょうほうふぐほうしんにぐちゅういください。ゆーゆでぃくぃみそーれー。
@@ -369,7 +369,7 @@
     <string name="max_retry_desc">ダウンロードちゅうしするまでぃぬさちゃるいさいしこうかいすん</string>
     <string name="file_deleted">ファイルさちゅるじょさびたん</string>
     <string name="app_update_notification_channel_name">アプリぬこうしんちうち</string>
-    <string name="download_to_sdcard_error_message">やしがいぶSDカードんかいダウンロードなやびらん。ダウンロードフォルダーぬばしょリセットさびーが\?</string>
+    <string name="download_to_sdcard_error_message">やしがいぶSDカードんかいダウンロードなやびらん。ダウンロードフォルダーぬばしょリセットさびーが?</string>
     <string name="saved_tabs_invalid_json">ふずんさったるタブゆみくみらんたみ、デフォルトぬタブしーようさびーん</string>
     <string name="main_page_content_summary">メインページんかいひょうじさりーるタブ</string>
     <string name="updates_setting_description">みーさるバージョンがいようかのうなとぅちにアプリぬこうしんかくにんすんちうちひょうじさびーん</string>
@@ -394,7 +394,7 @@
     <string name="error_postprocessing_stopped">ファイルぬさぎょうちゅうにNewPipeぬくーららりやびたん</string>
     <string name="error_insufficient_storage">デバイスんかいにりらりょうぬあいびらん</string>
     <string name="error_progress_lost">ファイルぬさちゅるじょさったるたみ、しんこうじょうちゅーがうしならりやびたん</string>
-    <string name="confirm_prompt">ダウンロードりりき、あらんでぃダウンロードさるファイルしーょうきょさびーが\?</string>
+    <string name="confirm_prompt">ダウンロードりりき、あらんでぃダウンロードさるファイルしーょうきょさびーが?</string>
     <string name="enable_queue_limit">ダウンロードキューぬせいぎん</string>
     <string name="enable_queue_limit_desc">どうじーがてぃーちぬダウンロードぬじっこうさりやびーん</string>
     <string name="start_downloads">ダウンロードぬかいし</string>
@@ -406,7 +406,7 @@
     <string name="downloads_storage_use_saf_summary">ストレージアクセスフレームワークー、がいぶSDカードんかいぬダウンロードぬかのうやいびーん</string>
     <string name="clear_playback_states_title">さいせいいちのさくじょ</string>
     <string name="clear_playback_states_summary">まじりぬさいせいいちさちゅるじょさびーん</string>
-    <string name="delete_playback_states_alert">まじりぬさいせいいちさちゅるじょさびーが\?</string>
+    <string name="delete_playback_states_alert">まじりぬさいせいいちさちゅるじょさびーが?</string>
     <string name="drawer_header_description">サービスぬきりけーい、ぎんじぇーぬしんたく:</string>
     <plurals name="videos">
         <item quantity="one">%sふんぬちゃーしが</item>
@@ -541,7 +541,7 @@
     <string name="unsupported_url_dialog_message">URLにいるしちなやびらんたん。ふかぬアプリっしふぃらちゃびーが?</string>
     <string name="notification_scale_to_square_image_summary">ちまーにひょうじさりーるサムネイル16:9からせいほうけいなさびーん</string>
     <string name="notification_scale_to_square_image_title">サムネイルせいほうけいなすん</string>
-    <string name="notification_actions_summary">いかタップしちうちぬアクションへんしゅうさびーん。みぎぬわんかいあんチェックボックスしーようしコンパクトやるちまーにひょうじすし3ちまっししんたくさびーん</string>
+    <string name="notification_actions_summary">いかタップしちうちぬアクションへんしゅうさびーん。みぎぬわんかいあんチェックボックスしーようしコンパクトやるちまーにひょうじすし3ちまっししんたくさびーん。</string>
     <string name="notification_actions_at_most_three">コンパクトやるちまーにひょうじさりーるアクションー3ちまっしいらぶるくとぅがなやびーん!</string>
     <string name="notification_action_4_title">5ばんみぬアクションボタン</string>
     <string name="notification_action_3_title">4ばんみぬアクションボタン</string>
@@ -718,9 +718,9 @@
     <string name="duplicate_in_playlist">はいーらでぃひょうじさっとーるプレイリストんかえー、しでぃにくぬアイテムぬくくまっとーいびーん。</string>
     <string name="playlist_add_stream_success_duplicate">%dかいじゅうふくちいかさりやびたん</string>
     <string name="unset_playlist_thumbnail">くていサムネイルぬしっていこーいじょ</string>
-    <string name="remove_duplicates_title">じゅうふくさくじょさびーが\?</string>
+    <string name="remove_duplicates_title">じゅうふくさくじょさびーが?</string>
     <string name="ignore_hardware_media_buttons_summary">たとぅいねー、ぶちりボタンぬやんでぃたるヘッドセットしーようそーーあいにびんりやいびーん</string>
-    <string name="remove_duplicates_message">くぬさいせいリストねーんぬじゅうふくさるストリームまじりさくじょさびーが\?</string>
+    <string name="remove_duplicates_message">くぬさいせいリストねーんぬじゅうふくさるストリームまじりさくじょさびーが?</string>
     <string name="ignore_hardware_media_buttons_title">ハードウェアメディアボタンイベントむしすん</string>
     <string name="remove_duplicates">じゅうふくさちゅるじょ</string>
     <string name="feed_hide_streams_title">ちぎぬストリームひょうじ</string>
@@ -806,4 +806,11 @@
     <string name="share_playlist">プレイリストちゅーゆーいん</string>
     <string name="share_playlist_with_titles_message">プレイリストめいてぃがろービデオタイトルんでーぬしょうさいくくむるプレイリスト、あらんでぃビデオURLぬみぬシンプルやるリストとぅしてぃプレイリストちゅーゆーいんさびーん</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%sぬへんしん</item>
+        <item quantity="other">%sぬへんしん</item>
+    </plurals>
+    <string name="show_more">なーふぃんんーじゅん</string>
+    <string name="show_less">ひょうじいきらくすん</string>
+    <string name="notification_actions_summary_android13">いかぬちうちアクションタップしへんしゅうさびーん。さいしょぬみーちぬアクション (さいせい/いちじていし、めーんかい、ちぎんかい)ーシステムにゆってぃしっていさりてぃうぅい、カスタマイズすしぇーなやびらん。</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml
index 836ab01c3..65dbffbae 100644
--- a/app/src/main/res/values-sat/strings.xml
+++ b/app/src/main/res/values-sat/strings.xml
@@ -59,7 +59,7 @@
     <string name="notification_action_2_title">ᱛᱮᱥᱟᱨ ᱠᱟᱹᱢᱤ ᱵᱩᱛᱟᱹᱢ</string>
     <string name="notification_action_1_title">ᱫᱚᱥᱟᱨ ᱠᱟᱹᱢᱤ ᱵᱩᱛᱟᱹᱢ</string>
     <string name="notification_action_0_title">ᱢᱟᱲᱟᱝ ᱠᱟᱹᱢᱤ ᱵᱩᱛᱟᱹᱢ</string>
-    <string name="notification_scale_to_square_image_title">ᱛᱷᱟᱹᱢᱵᱽᱱᱮᱞ ᱫᱚ ᱑:᱑ ᱮᱥᱯᱮᱠᱼᱴ ᱨᱮᱥᱤᱭᱚ ᱨᱮ ᱢᱟᱯ ᱢᱮ</string>
+    <string name="notification_scale_to_square_image_title">ᱛᱷᱟᱹᱢᱵᱽᱱᱮᱞ ᱫᱚ ᱑:᱑ ᱮᱥᱯᱮᱠᱼᱴ ᱨᱮᱥᱤᱭᱚ ᱨᱮ ᱜᱮᱫ ᱟᱠᱟᱱᱟ</string>
     <string name="ok">ᱴᱷᱤᱠ</string>
     <string name="play_audio">ᱥᱟᱰᱮ</string>
     <string name="mark_as_watched">ᱧᱮᱞ ᱠᱟᱱ ᱞᱮᱠᱷᱟ ᱪᱤᱱᱦᱟᱹ ᱠᱟᱜ ᱢᱮ</string>
@@ -69,4 +69,689 @@
     <string name="dark_theme_title">ᱧᱩᱛ</string>
     <string name="night_theme_title">ᱧᱩᱛ ᱛᱷᱤᱢ</string>
     <string name="black_theme_title">ᱦᱮᱱᱫᱮ</string>
+    <string name="high_quality_larger">ᱪᱮᱛᱟᱱ ᱥᱤᱠᱷᱱᱟᱹᱛ (ᱢᱟᱨᱟᱝ)</string>
+    <string name="audio_track_name">%1$s %2$s</string>
+    <string name="delete">ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="best_resolution">ᱡᱚᱛᱚ ᱠᱷᱚᱱ ᱱᱟᱯᱟᱭ ᱨᱩᱣᱟᱹ</string>
+    <string name="file_deleted">ᱦᱟᱹᱴᱤᱧ ᱪᱷᱟᱯᱟ ᱟᱠᱟᱱᱟ</string>
+    <string name="disable_media_tunneling_title">ᱢᱤᱰᱤᱭᱟ ᱴᱚᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="feed_update_threshold_option_always_update">ᱥᱟᱱᱟᱢ ᱚᱵᱚᱫᱷᱤ</string>
+    <string name="default_audio_format_title">ᱰᱤᱯᱷᱚᱞᱴ ᱥᱟᱰᱮ ᱯᱷᱚᱨᱢᱮᱴ</string>
+    <string name="enable_watch_history_title">ᱧᱮᱞ ᱱᱟᱜᱟᱢ</string>
+    <string name="resume_on_audio_focus_gain_title">ᱠᱷᱤᱞᱚᱱᱰ ᱮᱦᱚᱵ ᱢᱮ</string>
+    <string name="tracks">ᱴᱨᱮᱠ</string>
+    <string name="import_data_title">ᱤᱢᱯᱳᱨᱴ ᱰᱮᱴᱟᱵᱮᱥ</string>
+    <string name="general_error">ᱪᱷᱟᱸᱪ</string>
+    <string name="contribution_encouragement">ᱟᱢᱟᱜ ᱟᱭᱳ ᱟᱲᱟᱝ, ᱰᱤᱡᱟᱭᱤᱱ ᱵᱮᱵᱚᱥᱛᱟ, ᱠᱳᱰ ᱥᱟᱹᱠᱷᱭᱟᱹᱛ, ᱟᱨᱵᱟᱝ ᱟᱹᱰᱤ ᱢᱟᱨᱟᱝ ᱠᱳᱰ ᱵᱮᱵᱚᱥᱛᱟ ᱨᱮᱱᱟᱜ ᱟᱭᱳ ᱟᱲᱟᱝ ᱢᱮᱱᱟᱜᱼᱟ ⁇ ᱜᱚᱲᱚ ᱫᱚ ᱡᱚᱛᱚ ᱚᱠᱛᱚ ᱨᱮᱜᱮ ᱥᱟᱨᱦᱟᱣ ᱢᱮᱱᱟᱜᱼᱟ ᱾ ᱰᱤᱜᱟᱱ ᱠᱟᱹᱢᱤ ᱦᱩᱭ ᱞᱮᱱ ᱠᱷᱟᱱ ᱚᱱᱟ ᱜᱮ ᱱᱟᱯᱟᱭᱚᱜ-ᱟ ᱾</string>
+    <string name="view_on_github">GitHub ᱨᱮ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="most_liked">ᱡᱟᱹᱥᱛᱤ ᱞᱟᱭᱠᱟᱨ</string>
+    <string name="delete_downloaded_files">ᱰᱟᱩᱱᱞᱚᱰ ᱟᱠᱟᱱ ᱯᱷᱤᱞᱤᱯ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="delete_downloaded_files_confirm">ᱰᱤᱥᱠ ᱠᱷᱚᱱ ᱡᱚᱛᱚ ᱰᱟᱩᱱᱞᱚᱰ ᱟᱠᱟᱱ ᱯᱷᱤᱞ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="radio">ᱨᱮᱰᱤᱭᱳ</string>
+    <string name="settings_category_player_title">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ</string>
+    <string name="grid">ᱜᱽᱨᱤᱰ</string>
+    <string name="stop">ᱥᱴᱚᱯ</string>
+    <string name="enable_queue_limit">ᱰᱟᱩᱱᱞᱚᱰ ᱞᱤᱢᱤᱴᱮᱰ ᱠᱩᱣᱮ</string>
+    <string name="main_tabs_position_summary">ᱢᱩᱬᱩᱛ ᱴᱮᱵᱽ ᱵᱟᱪᱷᱟᱣᱤᱭᱟᱹ ᱫᱚ ᱞᱟᱛᱟᱨ ᱨᱮ ᱞᱟᱜᱟᱜ ᱢᱮ</string>
+    <string name="blank_page_summary">ᱵᱞᱮᱠ ᱥᱟᱦᱴᱟ</string>
+    <string name="card">ᱠᱟᱨᱰ</string>
+    <string name="no_video_streams_available_for_external_players">ᱵᱟᱦᱨᱮ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱵᱷᱤᱰᱤᱭᱳ ᱥᱴᱨᱤᱢ ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="select_quality_external_players">ᱵᱟᱦᱨᱮ ᱮᱱᱮᱡᱤᱭᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="unknown_format">ᱵᱟᱝ ᱵᱟᱰᱟᱭ ᱟᱠᱟᱱ ᱯᱷᱚᱨᱢᱮᱴ</string>
+    <string name="no_streams">ᱵᱟᱹᱱᱩᱜᱼᱟ ᱫᱟᱜ</string>
+    <string name="msg_popup_permission">ᱱᱚᱶᱟ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ_x000D_
+\nᱯᱚᱯ-ᱟᱯ ᱢᱚᱰ ᱨᱮ ᱮᱛᱦᱚᱵ ᱢᱮ</string>
+    <string name="subtitle_activity_recaptcha">ᱡᱚᱠᱷᱚᱱ ᱥᱟᱦᱟᱣ ᱟᱠᱟᱱᱟ \"Done\" ᱚᱞ ᱢᱮ</string>
+    <string name="give_back">ᱨᱩᱣᱟ.</string>
+    <string name="select_a_kiosk">ᱠᱤᱭᱳᱥᱠ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="new_and_hot">ᱱᱟᱣᱟ ᱟᱨ ᱦᱮᱴ</string>
+    <string name="recently_added">ᱱᱟᱦᱟᱜ ᱥᱮᱞᱮᱫ ᱟᱠᱟᱱᱟ</string>
+    <string name="show_channel_details">ᱪᱟᱱᱮᱞ ᱵᱟᱰᱟᱭ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="enqueue_stream">ᱤᱱᱠᱭᱩᱤᱝ</string>
+    <string name="enqueued">ᱮᱱᱠᱭᱩᱣᱮᱰ</string>
+    <string name="start_here_on_background">ᱚᱛᱱᱚ ᱨᱮ ᱥᱮᱞᱮᱫ ᱮᱛᱚᱦᱚᱵ ᱢᱮ</string>
+    <string name="always_ask_open_action">ᱡᱚᱛᱚ ᱚᱠᱛᱚ ᱯᱮᱨᱮᱡ ᱢᱮ</string>
+    <string name="preferred_player_fetcher_notification_title">ᱤᱱᱯᱷᱳ ᱧᱟᱢ…</string>
+    <string name="preferred_player_fetcher_notification_message">ᱞᱟᱹᱠᱛᱤ ᱟᱠᱟᱱ ᱥᱟᱦᱴᱟ ᱞᱚᱰ ᱮᱱᱟ</string>
+    <string name="name">ᱧᱩᱛᱩᱢ</string>
+    <string name="add_to_playlist">ᱥᱮᱨᱮᱧ ᱞᱤᱥᱴᱤ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ</string>
+    <string name="processing_may_take_a_moment">ᱠᱟᱹᱢᱤᱦᱚᱨᱟ... ᱚᱠᱛᱚ ᱫᱷᱟᱹᱵᱤᱡ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ</string>
+    <string name="show_crash_the_player_title">ᱠᱨᱟᱥ ᱴᱷᱮᱱ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ</string>
+    <string name="disable_media_tunneling_automatic_info">ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱨᱮ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱫᱚ ᱰᱤᱯᱚᱞᱴ ᱞᱮᱠᱟᱛᱮ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ ᱪᱮᱫᱟᱜ ᱥᱮ ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱢᱚᱰᱮᱞ ᱱᱚᱶᱟ ᱵᱟᱭ ᱥᱚᱦᱚᱫᱟ ᱾</string>
+    <string name="crash_the_app">ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="show_error_snackbar">ᱢᱤᱫ error ᱥᱱᱮᱠᱵᱟᱨ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="import_soundcloud_instructions">URL ᱥᱮ ᱟᱢᱟᱜ ID ᱚᱞ ᱠᱟᱛᱮᱜ ᱢᱤᱫ SoundCloud ᱯᱨᱚᱯᱷᱟᱭᱤᱞ ᱮᱢ ᱢᱮ:_x000D_
+\n_x000D_
+\n1. ᱢᱤᱫ ᱣᱮᱵᱽ-ᱵᱽᱨᱟᱣᱡᱚᱨ ᱨᱮ \"ᱰᱮᱥᱠᱴᱚᱯ ᱢᱳᱰ\" ᱮᱱᱮᱡ ᱢᱮ (ᱡᱚᱵᱮᱛᱟᱨᱮ ᱢᱚᱵᱟᱭᱤᱞ ᱰᱤᱵᱟᱤᱥ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱝ ᱧᱟᱢᱚᱜᱼᱟ)_x000D_
+\n2. ᱱᱚᱶᱟ URL ᱨᱮ ᱪᱟᱞᱟᱜ ᱢᱮ: %1$s_x000D_
+\n3. ᱞᱚᱜᱤᱱ ᱢᱮ ᱡᱚᱠᱷᱚᱱ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ_x000D_
+\n4. ᱟᱢ ᱚᱠᱟ ᱨᱮ ᱥᱮᱞᱮᱫ ᱞᱮᱱᱟᱢ ᱚᱱᱟ ᱯᱨᱚᱯᱷᱟᱭᱤᱞ URL ᱠᱚᱯᱤ ᱢᱮ.</string>
+    <string name="playback_speed_control">ᱯᱞᱮᱵᱮᱠ ᱥᱯᱤᱰ ᱠᱚᱱᱴᱨᱚᱞ</string>
+    <string name="playback_tempo">ᱴᱮᱢᱯᱳ</string>
+    <string name="unhook_checkbox">ᱵᱚᱫᱚᱞ ᱢᱮ (ᱵᱚᱫᱚᱞ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ)</string>
+    <string name="skip_silence_checkbox">ᱥᱤᱛᱩᱝ ᱚᱠᱛᱚ ᱨᱮ ᱯᱷᱮᱥᱯᱷᱚᱨᱰ ᱢᱮ</string>
+    <string name="percent">ᱯᱨᱚ᱐ᱤᱥᱚᱫ</string>
+    <string name="accept">ᱮᱠᱥᱮᱯᱴ</string>
+    <string name="enable_streams_notifications_title">ᱱᱟᱣᱟ ᱥᱴᱨᱚᱢ ᱨᱮᱭᱟᱜ ᱠᱷᱚᱵᱚᱨ</string>
+    <string name="streams_notifications_interval_title">ᱪᱟᱪᱞᱟᱣ ᱚᱵᱚᱥᱛᱟ</string>
+    <string name="manual_update_title">ᱱᱟᱣᱟ ᱥᱟᱦᱴᱟ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="paused">ᱛᱷᱟᱯᱚᱱ</string>
+    <string name="overwrite_unrelated_warning">ᱱᱚᱶᱟ ᱧᱩᱛᱩᱢ ᱟᱱᱟᱜ ᱢᱤᱫ ᱯᱷᱤᱞ ᱢᱮᱱᱟᱜᱼᱟ</string>
+    <string name="overwrite_finished_warning">ᱱᱚᱶᱟ ᱧᱩᱛᱩᱢ ᱟᱱ ᱢᱤᱫ ᱰᱟᱩᱱᱞᱚᱰ ᱟᱠᱟᱱ ᱯᱷᱤᱞ ᱢᱮᱱᱟᱜᱼᱟ</string>
+    <string name="error_ssl_exception">ᱥᱤᱠᱷᱱᱟᱹᱛ ᱡᱚᱱᱚᱲᱟᱣ ᱵᱟᱭ ᱛᱮᱭᱟᱨ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="downloads_storage_ask_title">ᱡᱟᱦᱟᱸ ᱨᱮ ᱰᱟᱩᱱᱞᱚᱰ ᱢᱮ</string>
+    <string name="new_seek_duration_toast">ᱮᱠᱥᱳᱯᱞᱮᱭᱟᱨ ᱞᱟᱹᱠᱛᱤᱭᱟᱜ ᱞᱟᱹᱠᱛᱤᱭᱟᱜ ᱞᱟᱹᱠᱛᱤᱭᱟᱜ ᱚᱠᱛᱚ %d ᱥᱮᱠᱮᱱᱰ ᱨᱮ ᱞᱟᱜᱟᱣ ᱟᱠᱟᱱᱟ</string>
+    <string name="fragment_feed_title">ᱪᱮᱫ ᱱᱟᱣᱟ</string>
+    <string name="selected_stream_external_player_not_supported">ᱵᱟᱦᱨᱮ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹᱠᱚ ᱵᱟᱠᱚ ᱥᱚᱦᱚᱫ ᱟᱠᱟᱫ ᱪᱷᱟᱸᱪ</string>
+    <string name="no_audio_streams_available_for_external_players">ᱵᱟᱦᱨᱮ ᱯᱞᱮᱭᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱚᱰᱤᱭᱚ ᱥᱴᱨᱤᱢ ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="audio_track_type_original">ᱚᱥᱚᱞ</string>
+    <string name="songs">ᱥᱮᱨᱮᱧ</string>
+    <string name="recent">ᱱᱟᱣᱟ</string>
+    <string name="notification_actions_summary_android13">ᱱᱚᱶᱟ ᱨᱮ ᱴᱟᱯ ᱠᱟᱛᱮ ᱞᱟᱛᱟᱨ ᱨᱮ ᱮᱢ ᱟᱠᱟᱱ ᱡᱚᱛᱚ ᱱᱤᱯᱷᱩᱴ ᱮᱠᱥᱚᱱ ᱥᱟᱯᱲᱟᱣ ᱢᱮ ᱾ ᱯᱩᱭᱞᱩ ᱯᱮᱭᱟ ᱠᱟᱹᱢᱤ (ᱯᱞᱮ/ᱯᱷᱟᱣᱩᱥ, ᱯᱩᱭᱞᱩ ᱟᱨ ᱛᱟᱭᱚᱢ) ᱫᱚ ᱥᱤᱥᱴᱚᱢ ᱦᱚᱛᱮᱛᱮ ᱥᱮᱴᱮᱨ ᱟᱠᱟᱱᱟ ᱟᱨ ᱱᱚᱣᱟ ᱠᱚ ᱟᱹᱨᱤᱪᱟᱹᱞᱤ ᱛᱮ ᱵᱟᱭ ᱵᱚᱫᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱾</string>
+    <string name="notification_colorize_summary">ᱟᱱᱰᱨᱚᱭᱮᱰ ᱴᱷᱟᱹᱣᱠᱟᱹ ᱟᱠᱟᱱ ᱪᱤᱛᱟᱹᱨ ᱨᱮ ᱢᱩᱬ ᱪᱤᱛᱟᱹᱨ ᱞᱮᱠᱟᱛᱮ ᱱᱤᱯᱷᱩᱴ ᱪᱤᱛᱟᱹᱨ ᱨᱮᱱᱟᱜ ᱨᱚᱝ ᱵᱮᱱᱟᱣ ᱢᱮ (ᱱᱚᱶᱟ ᱡᱚᱛᱚ ᱰᱤᱵᱟᱤᱥ ᱨᱮ ᱵᱟᱝ ᱧᱟᱢᱚᱜᱼᱟ)</string>
+    <string name="popup_remember_size_pos_title">ᱴᱟᱴᱠᱟ ᱜᱩᱱᱠᱚ ᱢᱚᱱᱮ ᱠᱟᱜ ᱢᱮ</string>
+    <string name="seek_duration_title">ᱯᱷᱮᱥᱯᱷᱚᱨᱰ/ᱨᱟᱭᱣᱤᱞᱰ ᱥᱮᱪᱮᱛ ᱚᱠᱛᱚ</string>
+    <string name="ignore_hardware_media_buttons_summary">ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ, ᱡᱩᱫᱤ ᱟᱢ ᱡᱟᱦᱟᱸᱭ ᱵᱷᱤᱛᱨᱤ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱ ᱵᱷᱤᱛᱨᱤ ᱵᱚᱫᱚᱞ ᱛᱮ ᱦᱮᱰᱥᱮᱴ ᱠᱚ ᱵᱮᱵᱷᱟᱨ ᱮᱫᱟ</string>
+    <string name="show_comments_title">ᱴᱤᱯᱚᱬᱤ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="show_description_summary">ᱵᱷᱤᱰᱤᱭᱳ ᱵᱟᱵᱚᱫᱽ ᱟᱨ ᱮᱴᱟᱜ ᱵᱟᱰᱟᱭ ᱞᱟᱹᱜᱤᱫ ᱱᱚᱶᱟ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="thumbnail_cache_wipe_complete_notice">ᱪᱤᱛᱟᱹᱨ ᱠᱮᱪ ᱵᱚᱫᱚᱞ</string>
+    <string name="metadata_cache_wipe_title">ᱠᱟᱪᱷ ᱢᱮᱴᱟᱰᱮᱴᱟ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="metadata_cache_wipe_summary">ᱥᱟᱱᱟᱢ ᱠᱮᱪ ᱟᱠᱟᱱ ᱣᱮᱵᱽ ᱯᱮᱡ ᱨᱮᱱᱟᱜ ᱰᱮᱴᱟ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="left_gesture_control_summary">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱥᱠᱨᱤᱱ ᱨᱮᱱᱟᱜ ᱪᱮᱛᱟᱱ ᱨᱮ ᱪᱤᱱᱦᱟᱹ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="volume">ᱵᱷᱚᱞᱚᱢ</string>
+    <string name="none">ᱡᱟᱦᱟᱱ ᱵᱟᱝ</string>
+    <string name="enable_search_history_title">ᱛᱚᱨᱡᱚᱢᱟ ᱱᱟᱜᱟᱢ</string>
+    <string name="enable_search_history_summary">ᱞᱚᱠᱟᱞ ᱞᱮᱠᱟᱛᱮ ᱥᱩᱪᱚᱱᱟ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="enable_playback_state_lists_title">ᱞᱤᱥᱴᱤ ᱨᱮ ᱯᱳᱡᱤᱥᱚᱱ</string>
+    <string name="enable_playback_state_lists_summary">ᱞᱤᱥᱴᱤ ᱨᱮ ᱯᱞᱮᱼᱯᱷᱚᱞ ᱯᱳᱡᱤᱥᱚᱱ ᱤᱱᱰᱮᱠᱴᱚᱨ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="settings_category_clear_data_title">ᱥᱟᱱᱛᱟᱲᱤ ᱚᱱᱚᱞ</string>
+    <string name="show_hold_to_append_title">enqueue ᱴᱤᱯ ᱞᱟᱹᱜᱤᱫ ᱫᱚᱦᱚᱭ ᱢᱮ</string>
+    <string name="show_hold_to_append_summary">Details: ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱨᱮ ᱯᱷᱚᱱ ᱥᱮ ᱯᱚᱯᱯᱟᱯ ᱵᱩᱛᱟᱹᱢ ᱞᱟᱜᱟᱣ ᱞᱮᱠᱷᱟᱱ ᱴᱤᱯ ᱮᱢ ᱢᱮ</string>
+    <string name="peertube_instance_url_help">%s ᱨᱮ ᱟᱢᱟᱜ ᱞᱟᱹᱠᱛᱤ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="settings_category_history_title">ᱱᱟᱜᱟᱢ ᱟᱨ ᱠᱷᱟᱪ</string>
+    <string name="settings_category_appearance_title">ᱧᱮᱧᱮᱞ</string>
+    <string name="settings_category_debug_title">ᱰᱤᱵᱩᱜ</string>
+    <string name="settings_category_player_notification_title">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱵᱟᱰᱟᱭ</string>
+    <string name="background_player_playing_toast">ᱛᱷᱚᱠ ᱨᱮ ᱥᱮᱞᱮᱫᱚᱜ</string>
+    <string name="restricted_video">ᱱᱚᱶᱟ ᱵᱷᱤᱰᱤᱭᱳ ᱫᱚ ᱩᱢᱮᱨ ᱦᱟᱹᱴᱤᱧ ᱠᱟᱱᱟ._x000D_
+\n_x000D_
+\nᱡᱩᱫᱤ ᱟᱢ ᱱᱚᱶᱟ ᱧᱮᱞᱢᱮ ᱠᱷᱟᱱ ᱟᱢᱟᱜ ᱥᱤᱴᱤᱝ ᱨᱮ %1$s\" ᱚᱞ ᱢᱮ ᱾\"</string>
+    <string name="downloads">ᱰᱟᱩᱱᱞᱚᱰ</string>
+    <string name="downloads_title">ᱰᱟᱩᱱᱞᱚᱰ</string>
+    <string name="albums">ᱟᱞᱵᱚᱢ</string>
+    <string name="artists">ᱟᱨᱴᱤᱥᱴ ᱠᱚ</string>
+    <string name="disabled">ᱵᱷᱮᱜᱟᱨ ᱦᱚᱲ</string>
+    <string name="clear">ᱠᱞᱟᱨ</string>
+    <string name="notification_channel_description">ᱱᱤᱭᱩ ᱯᱟᱭᱯᱮ ᱨᱮᱱᱟᱜ ᱯᱞᱮᱭᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱱᱳᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ</string>
+    <string name="hash_channel_description">ᱵᱷᱤᱰᱤᱭᱳ ᱦᱟᱥᱤᱝ ᱯᱨᱚᱜᱨᱮᱥ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱚᱵᱚᱨ</string>
+    <string name="streams_notification_channel_name">ᱱᱟᱣᱟ ᱫᱟᱨᱮ ᱠᱚ</string>
+    <string name="streams_notification_channel_description">ᱥᱮᱞᱮᱫ ᱞᱟᱹᱜᱤᱫ ᱱᱟᱣᱟ ᱥᱴᱨᱚᱢ ᱠᱚ ᱵᱟᱰᱟᱭ ᱦᱚᱪᱚ</string>
+    <string name="error_report_channel_description">ᱵᱟᱹᱲᱤᱡ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱚᱵᱚᱨ ᱠᱚ</string>
+    <string name="unknown_content">[ᱱᱚᱣᱟ ᱵᱟᱰᱟᱭᱚᱜ ᱠᱟᱱᱟ]</string>
+    <string name="clear_cookie_title">reCAPTCHA ᱠᱩᱠᱤ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="recaptcha_cookies_cleared">reCAPTCHA ᱠᱩᱠᱤ ᱠᱚ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="export_data_summary">ᱮᱠᱥᱯᱳᱨᱴ ᱦᱤᱥᱛᱟ, ᱥᱚᱵᱽᱥᱠᱨᱟᱭᱵᱮᱥᱚᱱ, ᱯᱞᱮ ᱞᱤᱥᱴ ᱟᱨ ᱥᱮᱴᱤᱝ</string>
+    <string name="clear_views_history_title">ᱧᱮᱞ ᱛᱟᱵᱩᱱ ᱯᱮ ᱱᱟᱜᱟᱢ</string>
+    <string name="clear_views_history_summary">ᱠᱷᱮᱞ ᱟᱠᱟᱱ ᱥᱴᱨᱤᱢ ᱨᱮᱱᱟᱜ ᱱᱟᱜᱟᱢ ᱟᱨ ᱠᱷᱮᱞ ᱟᱠᱟᱱ ᱯᱳᱡᱤᱥᱚᱱ ᱠᱚ ᱵᱚᱫᱚᱞᱟ</string>
+    <string name="delete_view_history_alert">ᱥᱟᱱᱟᱢ ᱧᱮᱞ ᱱᱟᱜᱟᱢ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="watch_history_deleted">ᱧᱮᱞ ᱛᱟᱵᱩᱱ ᱯᱮ</string>
+    <string name="download_to_sdcard_error_title">ᱵᱟᱦᱨᱮ ᱥᱴᱳᱨᱮᱡ ᱵᱟᱭ ᱧᱟᱢᱚᱜ</string>
+    <string name="search_history_deleted">ᱧᱮᱞ ᱛᱟᱵᱩᱱ ᱯᱮ ᱺ</string>
+    <string name="fast_mode">ᱦᱟᱛᱟᱝ ᱢᱚᱰ</string>
+    <string name="main_tabs_position_title">ᱢᱩᱬᱩᱛ ᱴᱮᱵᱽ ᱨᱮᱱᱟᱜ ᱴᱷᱟᱶ</string>
+    <string name="download_to_sdcard_error_message">ᱵᱟᱦᱨᱮ SD ᱠᱟᱨᱰ ᱨᱮ ᱰᱟᱩᱱᱞᱚᱰ ᱦᱚᱪᱚ ᱵᱟᱝ ᱜᱟᱱᱚᱜ ᱟ. ᱰᱟᱩᱱᱞᱚᱰ ᱯᱷᱳᱞᱰᱟᱨ ᱞᱚᱠᱮᱥᱚᱱ ᱨᱤᱥᱮᱴ ᱢᱮ ?</string>
+    <string name="could_not_load_thumbnails">ᱡᱚᱛᱚ ᱪᱤᱛᱟᱹᱨ ᱠᱚ ᱞᱚᱰ ᱵᱟᱝ ᱜᱟᱱᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="parsing_error">ᱣᱮᱵᱽᱥᱟᱭᱤᱴ ᱵᱟᱭ ᱯᱟᱨᱥ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="content_not_available">ᱥᱟᱦᱴᱟ ᱵᱟᱹᱱᱩᱜ-ᱟ</string>
+    <string name="external_player_unsupported_link_type">ᱵᱟᱦᱨᱮ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱚ ᱱᱚᱝᱠᱟᱱ ᱡᱚᱱᱚᱲ ᱠᱚ ᱵᱟᱠᱚ ᱥᱚᱦᱚᱫᱟ</string>
+    <string name="video_streams_empty">ᱡᱟᱦᱟᱱ ᱵᱷᱤᱰᱤᱭᱳ ᱥᱴᱨᱤᱢ ᱵᱟᱝ ᱧᱟᱢ ᱟᱠᱟᱱᱟ</string>
+    <string name="audio_streams_empty">ᱚᱰᱤᱭᱳ ᱥᱴᱨᱤᱢ ᱵᱟᱝ ᱧᱟᱢ ᱟᱠᱟᱱᱟ</string>
+    <string name="error_report_notification_toast">ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱞᱮᱱᱟ, ᱱᱤᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="error_report_open_issue_button_text">GitHub ᱨᱮ ᱚᱱᱚᱞ</string>
+    <string name="error_snackbar_action">ᱨᱤᱯᱚᱨᱴ</string>
+    <string name="info_labels">What:\\nRequest:\\nContent Language:\\nContent ᱫᱤᱥᱚᱢ:\\nApp Language:\\nService:\\nGMT ᱚᱠᱛᱚ:\\nPackage:\\nVersion:\\nOS version:</string>
+    <string name="your_comment">ᱟᱢᱟᱜ ᱯᱟᱹᱛᱭᱟᱹᱣ (ᱤᱝᱞᱤᱥ ᱛᱮ):</string>
+    <string name="detail_uploader_thumbnail_view_description">ᱚᱯᱞᱚᱰᱟᱨᱟᱜ ᱟᱵᱟᱛᱟᱨ ᱛᱷᱩᱱᱤᱠᱟ</string>
+    <string name="detail_likes_img_view_description">ᱞᱟᱭᱠ</string>
+    <string name="search_no_results">ᱡᱟᱦᱟᱱ ᱡᱤᱱᱤᱥ</string>
+    <string name="empty_list_subtitle">ᱱᱚᱣᱟ ᱨᱮᱫᱚ ᱡᱟᱹᱥᱛᱤ ᱡᱟᱹᱥᱛᱤ ᱠᱨᱤᱠᱮᱴ ᱢᱮᱱᱟᱜᱼᱟ ᱾</string>
+    <string name="video">ᱵᱷᱤᱰᱤᱭᱳ</string>
+    <string name="short_thousand">k</string>
+    <string name="short_million">M</string>
+    <string name="subscribers_count_not_available">ᱥᱮᱞᱮᱫᱤᱭᱟᱹ ᱠᱚᱣᱟᱜ ᱞᱮᱠᱷᱟ ᱵᱟᱭ ᱦᱟᱹᱴᱤᱧ ᱟᱠᱟᱱᱟ</string>
+    <string name="no_views">ᱵᱟᱱᱩᱜ ᱧᱮᱞ</string>
+    <string name="no_videos">ᱵᱷᱤᱰᱤᱭᱳ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="more_than_100_videos">᱑᱐᱐+ ᱵᱷᱤᱰᱤᱭᱳ</string>
+    <string name="no_live_streams">ᱡᱤᱣᱤ ᱥᱴᱨᱤᱢ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="checksum">ᱪᱮᱠᱥᱢ</string>
+    <string name="dismiss">ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="msg_wait">ᱫᱟᱭᱟ ᱠᱟᱛᱮ ᱮᱛᱦᱚᱵ ᱢᱮ ᱾</string>
+    <string name="msg_copied">ᱠᱞᱤᱯᱵᱳᱨᱰ ᱨᱮ ᱠᱚᱯᱤ ᱟᱠᱟᱱᱟ</string>
+    <string name="title_activity_recaptcha">reCAPTCHA ᱪᱟᱞᱟᱣ</string>
+    <string name="settings_file_replacement_character_title">ᱵᱮᱱᱟᱣ ᱪᱤᱠᱤ</string>
+    <string name="settings_file_replacement_character_summary">ᱵᱟᱹᱞᱤ ᱪᱤᱠᱤ ᱠᱚ ᱱᱚᱶᱟ ᱢᱟᱹᱱ ᱥᱟᱞᱟᱜ ᱛᱮ ᱵᱚᱫᱚᱞᱚᱜᱼᱟ</string>
+    <string name="charset_letters_and_digits">ᱚᱞ ᱟᱨ ᱮᱞ</string>
+    <string name="charset_most_special_characters">ᱡᱟᱹᱥᱛᱤ ᱵᱤᱥᱮᱥ ᱪᱤᱠᱤ</string>
+    <string name="no_valid_zip_file">ᱪᱟᱹᱞᱩ ZIP ᱯᱷᱤᱞ ᱫᱚ ᱵᱟᱝ</string>
+    <string name="could_not_import_all_files">ᱞᱟᱹᱱᱟᱹᱤ: ᱡᱚᱛᱚ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱤᱱᱯᱷᱚᱨᱢ ᱵᱟᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ.</string>
+    <string name="override_current_data">ᱱᱚᱶᱟ ᱫᱚ ᱟᱢᱟᱜ ᱱᱮᱛᱟᱨᱟᱜ ᱥᱮᱴᱯᱟᱴ ᱩᱫᱩᱜ ᱮᱫᱟ ᱾</string>
+    <string name="import_settings">ᱟᱢ ᱥᱤᱴᱤᱝ ᱦᱚᱸ ᱟᱢᱯᱷᱮᱨᱟᱣ ᱦᱩᱭᱩᱜ ᱛᱟᱢᱟ?</string>
+    <string name="error_unable_to_load_comments">ᱠᱚᱢᱮᱱᱴ ᱞᱚᱰ ᱫᱚ ᱵᱟᱝ ᱜᱟᱱᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="top_50">ᱢᱩᱬ ᱕᱐</string>
+    <string name="conferences">ᱠᱚᱱᱯᱷᱮᱨᱮᱱᱥ</string>
+    <string name="title_activity_play_queue">ᱠᱷᱮᱞ ᱞᱟᱦᱟ</string>
+    <string name="play_queue_remove">ᱥᱟᱦᱟᱣ ᱢᱮ</string>
+    <string name="play_queue_stream_detail">ᱡᱚᱠᱷᱟ</string>
+    <string name="audio_track">ᱚᱰᱤᱭᱳ ᱴᱨᱟᱠ</string>
+    <string name="hold_to_append">ᱥᱮᱞᱮᱫᱚᱜ ᱞᱟᱹᱜᱤᱫ ᱫᱚᱦᱚᱭ ᱢᱮ</string>
+    <string name="enqueue_next_stream">ᱛᱟᱭᱚᱢ ᱛᱮ Enqueue</string>
+    <string name="enqueued_next">ᱛᱟᱭᱚᱢ ᱛᱮ ᱥᱮᱞᱮᱫ ᱢᱮ</string>
+    <string name="drawer_open">ᱚᱯᱮᱱ ᱰᱨᱟᱵᱷᱤᱴ</string>
+    <string name="preferred_open_action_settings_title">ᱵᱟᱹᱲᱛᱤ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ \"ᱚᱯᱮᱱ\" ᱠᱟᱹᱢᱤ</string>
+    <string name="preferred_open_action_settings_summary">⁇ %s ᱡᱚᱠᱷᱚᱱ ᱥᱟᱦᱴᱟ ᱵᱚᱫᱚᱞᱚᱜ-ᱟ</string>
+    <string name="video_player">ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱟᱨ</string>
+    <string name="background_player">ᱯᱟᱛᱷᱟᱢ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ</string>
+    <string name="popup_player">ᱯᱚᱯᱯᱟᱯ ᱯᱞᱮᱭᱟᱨ</string>
+    <string name="duplicate_in_playlist">ᱥᱟᱨᱮᱡ ᱟᱠᱟᱱ ᱯᱞᱮ ᱞᱤᱥᱴᱤ ᱨᱮ ᱱᱤᱛᱚᱜ ᱱᱚᱣᱟ ᱡᱤᱱᱤᱥ ᱢᱮᱱᱟᱜᱼᱟ ᱾</string>
+    <string name="rename_playlist">ᱧᱩᱛᱩᱢ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="unmute">Unmute</string>
+    <string name="unset_playlist_thumbnail">ᱵᱟᱝ ᱥᱮᱴᱮᱨ ᱟᱠᱟᱱ ᱟᱹᱪᱩᱨ ᱦᱟᱹᱴᱤᱧ</string>
+    <string name="playlist_add_stream_success">ᱯᱮᱭᱞᱤᱥᱴ</string>
+    <string name="playlist_add_stream_success_duplicate">%d ᱚᱠᱛᱚ ᱵᱟᱨᱟᱵᱟᱨᱤ ᱥᱮᱞᱮᱫᱚᱜᱼᱟ</string>
+    <string name="playlist_creation_success">ᱪᱤᱛᱟᱹᱨ ᱵᱮᱱᱟᱣ ᱟᱠᱟᱱᱟ</string>
+    <string name="playlist_thumbnail_change_success">ᱥᱮᱨᱮᱧ ᱞᱤᱥᱴᱤ ᱨᱮᱱᱟᱜ ᱛᱷᱤᱢᱵᱽᱱᱟᱭᱟᱨ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ.</string>
+    <string name="playlist_no_uploader">ᱚᱴᱚ-ᱡᱮᱱᱮᱨᱮᱴ (ᱯᱷᱮᱰᱟᱛ ᱵᱟᱭ ᱧᱟᱢ ᱞᱮᱱᱟ)</string>
+    <string name="caption_none">ᱚᱞ ᱪᱤᱠᱤ ᱵᱟᱹᱱᱩᱜ</string>
+    <string name="resize_fit">ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ</string>
+    <string name="resize_fill">ᱯᱷᱤᱞᱰ</string>
+    <string name="resize_zoom">ᱡᱩᱢ</string>
+    <string name="caption_setting_description">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱜ ᱪᱷᱟᱸᱪ ᱚᱞ ᱨᱮᱱᱟᱜ ᱟᱥᱚᱞ ᱟᱨ ᱯᱟᱛᱷᱟᱢ ᱥᱴᱟᱭᱤᱞᱥ ᱵᱚᱫᱚᱞ ᱢᱮ. ᱪᱟᱹᱞᱩᱜ ᱞᱟᱹᱜᱤᱫ ᱮᱯ ᱮᱥᱴᱟᱨᱴ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ</string>
+    <string name="show_memory_leaks">ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="leak_canary_not_available">LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="enable_leak_canary_summary">ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="enable_disposed_exceptions_title">ᱡᱤᱭᱚᱱ ᱪᱤᱠᱤ ᱠᱷᱚᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱷᱮᱜᱟᱨ ᱠᱚ ᱚᱱᱚᱞ ᱢᱮ</string>
+    <string name="show_image_indicators_summary">ᱱᱮᱴᱣᱟᱨᱠ ᱞᱟᱹᱜᱤᱫ red, ᱰᱤᱥᱠ ᱞᱟᱹᱜᱤᱫ blue ᱟᱨ ᱢᱮᱢᱚᱨᱤ ᱞᱟᱹᱜᱤᱫ green</string>
+    <string name="show_crash_the_player_summary">ᱯᱞᱮᱭᱟᱨ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱮ ᱨᱮ ᱠᱨᱟᱥ ᱚᱯᱥᱚᱱ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="import_title">ᱤᱢᱯᱳᱨᱴ</string>
+    <string name="import_from">ᱤᱢᱯᱚᱨᱴ</string>
+    <string name="export_to">ᱮᱥᱯᱳᱨᱴ ᱢᱮ</string>
+    <string name="import_ongoing">ᱤᱢᱯᱳᱨᱴ…</string>
+    <string name="export_ongoing">ᱮᱠᱥᱯᱳᱨᱴ…</string>
+    <string name="import_file_title">ᱤᱢᱯᱳᱨᱴ ᱯᱷᱤᱞ</string>
+    <string name="previous_export">ᱢᱟᱲᱟᱝ ᱮᱠᱥᱯᱚᱨᱴ</string>
+    <string name="subscriptions_import_unsuccessful">ᱥᱚᱵᱥᱠᱨᱤᱯᱥᱚᱱ ᱮᱢ ᱫᱟᱲᱮᱭᱟᱜ ᱵᱟᱝ ᱠᱟᱱᱟ</string>
+    <string name="import_network_expensive_warning">\"ᱱᱚᱶᱟ ᱚᱯᱨᱮᱥᱚᱱ ᱨᱮ ᱱᱮᱴᱣᱟᱨᱠ ᱰᱷᱮᱨ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱢᱮᱱᱛᱮ ᱢᱟᱱᱟᱣ ᱢᱮ ᱾\"
+\n
+\nᱟᱢ ᱫᱚ ᱪᱮᱫ ᱮᱢ ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ?\"</string>
+    <string name="playback_reset">ᱨᱤᱥᱮᱴ</string>
+    <string name="semitone">ᱥᱮᱢᱤᱴᱚᱱ</string>
+    <string name="decline">ᱰᱷᱮᱨᱚᱜ</string>
+    <string name="limit_data_usage_none_description">ᱞᱤᱢᱤᱴ ᱵᱟᱹᱱᱩᱜ</string>
+    <string name="limit_mobile_data_usage_title">ᱢᱚᱵᱟᱤᱞ ᱰᱟᱴᱟ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱚ ᱨᱮ ᱞᱤᱢᱤᱴ ᱫᱟᱲᱮ</string>
+    <string name="manual_update_description">ᱱᱟᱣᱟ ᱵᱷᱮᱨᱥᱚᱱ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱢᱟᱱᱩᱣᱟᱞ ᱪᱮᱠ</string>
+    <string name="minimize_on_exit_title">ᱟᱯᱞᱤᱠᱮᱥᱚᱱ ᱥᱤᱴᱤᱪ ᱨᱮ ᱞᱟᱹᱴᱩ ᱦᱚᱪᱚ</string>
+    <string name="minimize_on_exit_summary">ᱢᱩᱬᱩᱛ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱟᱨ ᱠᱷᱚᱱ ᱮᱴᱟᱜ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱚᱜ ᱚᱠᱛᱚ ᱨᱮᱱᱟᱜ ᱠᱟᱹᱢᱤ ⁇ %s</string>
+    <string name="minimize_on_exit_none_description">ᱡᱟᱦᱟᱱ ᱵᱟᱝ</string>
+    <string name="minimize_on_exit_background_description">ᱚᱛᱱᱚ ᱯᱞᱮᱭᱟᱨ ᱨᱮ ᱞᱟᱹᱴᱩ ᱦᱚᱪᱚ</string>
+    <string name="wifi_only">Wi-Fi ᱨᱮᱜᱮ</string>
+    <string name="seekbar_preview_thumbnail_title">Seekbar thumbnail preview ᱧᱮᱞᱢᱮ</string>
+    <string name="low_quality_smaller">ᱠᱚᱢ ᱠᱣᱟᱞᱤᱴᱤ (ᱢᱤᱫ ᱠᱷᱚᱱ ᱠᱚᱢ)</string>
+    <string name="dont_show">ᱵᱟᱝ ᱧᱮᱞᱚᱜ</string>
+    <string name="app_update_unavailable_toast">ᱟᱢ NewPipe ᱨᱮᱭᱟᱜ ᱱᱟᱣᱟ ᱵᱟᱨᱥᱚᱱ ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="missions_header_pending">ᱯᱮᱱᱰᱤᱝ</string>
+    <string name="queued">ᱠᱩᱠᱞᱤ</string>
+    <string name="post_processing">ᱯᱟᱥᱴᱟ ᱯᱨᱚᱥᱟᱥᱚᱱ</string>
+    <string name="enqueue">ᱤᱱᱠᱭᱩᱤᱝ</string>
+    <string name="permission_denied">ᱥᱤᱥᱴᱮᱢ ᱦᱚᱛᱮᱛᱮ ᱵᱟᱝ ᱧᱟᱢ ᱟᱠᱟᱱ ᱠᱟᱹᱢᱤ</string>
+    <string name="overwrite_failed">ᱯᱷᱟᱹᱞ ᱪᱮᱛᱟᱱ ᱵᱟᱝ ᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="download_already_running">ᱱᱚᱶᱟ ᱧᱩᱛᱩᱢ ᱛᱮ ᱢᱤᱫ ᱰᱟᱩᱱᱞᱚᱰ ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="download_already_pending">ᱱᱚᱶᱟ ᱧᱩᱛᱩᱢ ᱛᱮ ᱢᱤᱫ ᱚᱞᱚᱜ ᱠᱟᱱ ᱰᱟᱩᱱᱞᱚᱰ ᱢᱮᱱᱟᱜᱼᱟ</string>
+    <string name="error_path_creation">ᱴᱷᱟᱶ ᱯᱷᱳᱞᱰᱚᱨ ᱛᱮᱭᱟᱨ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ</string>
+    <string name="error_download_resource_gone">ᱱᱚᱶᱟ ᱰᱟᱩᱱᱞᱚᱰ ᱫᱚ ᱵᱟᱭ ᱨᱤᱠᱳᱨᱰ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="clear_download_history">ᱰᱟᱩᱱᱞᱚᱰ ᱦᱤᱥᱛᱟ ᱪᱷᱟᱯᱟ ᱢᱮ</string>
+    <string name="max_retry_msg">ᱟᱹᱰᱤ ᱡᱟᱹᱥᱛᱤ ᱫᱟᱲᱮ</string>
+    <string name="pause_downloads_on_mobile">ᱢᱤᱴᱟᱨ ᱛᱮᱭᱟᱨ ᱟᱠᱟᱱ ᱱᱮᱴᱣᱟᱨᱠ ᱨᱮ ᱵᱚᱫᱚᱞ</string>
+    <string name="start_downloads">ᱰᱟᱩᱱᱞᱚᱰ ᱮᱦᱚᱵ ᱢᱮ</string>
+    <string name="remove_watched">ᱧᱮᱞᱚᱜ ᱟᱠᱟᱱ ᱥᱟᱯᱲᱟᱣ ᱢᱮ</string>
+    <string name="remove_watched_popup_title">ᱧᱮᱞ ᱟᱠᱟᱱ ᱵᱷᱤᱰᱤᱭᱳ ᱠᱚ ᱪᱷᱩᱴᱟᱹᱣ?</string>
+    <string name="remove_duplicates">ᱫᱩᱯᱞᱟᱹᱲ ᱠᱚ ᱦᱮᱡ ᱢᱮ</string>
+    <string name="remove_watched_popup_yes_and_partially_watched_videos">ᱭ, ᱟᱨ ᱵᱷᱤᱰᱤᱭᱳ ᱠᱚ ᱡᱟᱦᱟᱸ ᱞᱮᱠᱟ ᱧᱮᱞᱚᱜᱼᱟ</string>
+    <string name="feed_groups_header_title">ᱪᱟᱱᱮᱞ ᱜᱨᱩᱯ</string>
+    <string name="feed_oldest_subscription_update">ᱯᱷᱤᱰ ᱢᱩᱪᱟᱹᱫ ᱵᱚᱫᱚᱞᱟᱠᱟᱱ: %s</string>
+    <string name="feed_subscription_not_loaded_count">ᱵᱟᱝ ᱞᱚᱰ ᱟᱠᱟᱱᱟ: %d</string>
+    <string name="feed_notification_loading">ᱯᱷᱤᱰ ᱞᱚᱰᱚᱜ ᱠᱟᱱᱟ…</string>
+    <string name="feed_processing_message">ᱠᱟᱹᱢᱤᱦᱚᱨᱟ ᱡᱚᱢᱟᱜ…</string>
+    <string name="feed_group_dialog_empty_name">ᱜᱩᱴ ᱧᱩᱛᱩᱢ ᱵᱟᱱᱩᱜᱼᱟ</string>
+    <string name="feed_group_dialog_delete_message">ᱟᱢ ᱱᱚᱶᱟ ᱜᱽᱨᱩᱯ ᱵᱚᱫᱚᱞ ᱢᱮᱢᱮ?</string>
+    <string name="feed_create_new_group_button_title">ᱱᱟᱣᱟ</string>
+    <string name="feed_use_dedicated_fetch_method_enable_button">ᱦᱟᱹᱴᱤᱧ ᱢᱚᱰ ᱨᱮ ᱥᱮᱞᱮᱫ ᱢᱮ</string>
+    <string name="feed_hide_streams_title">ᱞᱟᱛᱟᱨ ᱨᱮ ᱚᱞ ᱟᱠᱟᱱ ᱥᱴᱨᱤᱢ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="feed_fetch_channel_tabs">ᱪᱟᱱᱮᱞ ᱛᱟᱵᱽ ᱠᱚ ᱦᱟᱛᱟᱣ</string>
+    <string name="channel_created_by">%s ᱛᱮᱭᱟᱨ ᱟᱠᱟᱱᱟ</string>
+    <string name="video_detail_by">%s ᱦᱚᱛᱮᱛᱮ</string>
+    <string name="show_thumbnail_title">ᱛᱟᱢᱤᱞ ᱪᱤᱛᱟᱹᱨ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="chapters">ᱪᱮᱯᱴᱟᱨᱥ</string>
+    <string name="no_appropriate_file_manager_message_android_10">ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ ᱟᱢ ᱢᱤᱫ ᱯᱷᱤᱞ ᱢᱟᱱᱮᱡᱚᱨ ᱤᱱᱥᱴᱚᱞ ᱢᱮ ᱟᱨᱵᱟᱝ ᱰᱟᱩᱱᱞᱚᱰ ᱥᱤᱴᱤᱝ ᱨᱮ ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚ ᱞᱟᱹᱜᱤᱫ ᱯᱨᱚᱵᱷᱟᱣ ᱢᱮ\"</string>
+    <string name="youtube_music_premium_content">ᱱᱚᱶᱟ ᱵᱷᱤᱰᱤᱭᱳ ᱫᱚ ᱭᱩᱴᱭᱩᱵᱽ ᱢᱤᱣᱡᱤᱠ ᱯᱨᱤᱢᱤᱭᱟᱢ ᱥᱮᱞᱮᱫᱤᱭᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱜᱮ ᱧᱟᱢᱚᱜᱼᱟ, ᱚᱱᱟᱛᱮ ᱱᱚᱶᱟ ᱫᱚ ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯ ᱦᱚᱛᱮᱛᱮ ᱵᱟᱝ ᱥᱴᱨᱤᱢ ᱟᱨ ᱵᱟᱝ ᱰᱟᱩᱱᱞᱳᱰ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾</string>
+    <string name="service_provides_reason">%s ᱫᱚ ᱱᱚᱶᱟ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ:</string>
+    <string name="auto_device_theme_title">ᱚᱴᱚᱢᱟᱴᱤᱠ (ᱰᱤᱵᱟᱤᱥ ᱛᱷᱮᱢ)</string>
+    <string name="night_theme_summary">ᱟᱢᱟᱜ ᱯᱩᱭᱞᱩ ᱧᱤᱫᱟᱹ ᱛᱷᱤᱢ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ⁇ %s</string>
+    <string name="select_night_theme_toast">ᱟᱢ ᱞᱟᱛᱟᱨ ᱨᱮ ᱟᱢᱟᱜ ᱧᱤᱫᱟᱹ ᱪᱮᱛᱟᱱ ᱵᱟᱪᱷᱟᱣ ᱫᱟᱲᱮᱭᱟᱜ ᱟ</string>
+    <string name="description_select_disable">ᱪᱤᱛᱟᱹᱨ ᱨᱮ ᱚᱞ ᱪᱤᱠᱤ ᱵᱟᱪᱷᱟᱣ ᱫᱚᱦᱚᱭ ᱢᱮ</string>
+    <string name="metadata_category">ᱛᱷᱚᱠ</string>
+    <string name="metadata_licence">ᱞᱟᱭᱥᱮᱱᱥ</string>
+    <string name="metadata_avatars">ᱟᱵᱟᱛᱟᱨ</string>
+    <string name="metadata_banners">ᱵᱮᱱᱚᱨ</string>
+    <string name="metadata_privacy_unlisted">ᱵᱟᱝ ᱞᱤᱥᱴᱤ ᱟᱠᱟᱱᱟ</string>
+    <string name="notifications_disabled">ᱱᱤᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱠᱚ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="you_successfully_subscribed">ᱟᱢ ᱱᱤᱛᱚᱜ ᱱᱚᱣᱟ ᱪᱮᱱᱮᱞ ᱨᱮ ᱥᱮᱞᱮᱫ ᱟᱠᱟᱱᱟᱢ</string>
+    <string name="enumeration_comma">,</string>
+    <string name="select_audio_track_external_players">ᱵᱟᱦᱨᱮ ᱯᱞᱮᱭᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱚᱰᱤᱭᱚ ᱴᱨᱟᱠ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="unknown_quality">ᱵᱟᱝ ᱵᱟᱰᱟᱭ ᱧᱟᱢ ᱟᱠᱟᱱ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
+    <string name="unknown_audio_track">ᱵᱟᱝ ᱵᱟᱰᱟᱭ</string>
+    <string name="feed_show_watched">ᱡᱚᱛᱚ ᱛᱮ ᱧᱮᱞᱚᱜᱼᱟ</string>
+    <string name="always_use_exoplayer_set_output_surface_workaround_title">ExoPlayer ᱨᱮᱭᱟᱜ ᱵᱷᱤᱰᱤᱭᱳ ᱟᱯᱩᱴ ᱥᱩᱨᱥᱩᱯᱩᱨ ᱥᱤᱴᱤᱝ ᱣᱟᱨᱠᱟᱨᱚᱸᱜ ᱵᱮᱵᱷᱟᱨ ᱢᱮ</string>
+    <string name="channel_tab_videos">ᱵᱷᱤᱰᱤᱭᱳ</string>
+    <string name="channel_tab_tracks">ᱴᱨᱮᱠ</string>
+    <string name="channel_tab_shorts">ᱥᱟᱨᱴᱥ</string>
+    <string name="channel_tab_livestreams">ᱡᱤᱣᱤ</string>
+    <string name="channel_tab_channels">ᱪᱟᱱᱮᱞ</string>
+    <string name="channel_tab_playlists">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱚ</string>
+    <string name="channel_tab_albums">ᱟᱞᱵᱚᱢ</string>
+    <string name="channel_tab_about">ᱤᱫᱤ ᱠᱟᱛᱮ</string>
+    <string name="show_channel_tabs">ᱪᱟᱱᱮᱞ ᱛᱟᱵᱽ</string>
+    <string name="toggle_screen_orientation">ᱥᱠᱨᱤᱱ ᱴᱳᱜᱞ ᱳᱭᱨᱮᱱᱟᱥᱚᱱ</string>
+    <string name="previous_stream">ᱢᱟᱲᱟᱝ ᱥᱴᱨᱤᱢ</string>
+    <string name="forward">ᱯᱷᱚᱨᱣᱟᱨᱰ</string>
+    <string name="image_quality_title">ᱪᱤᱛᱟᱹᱨ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
+    <string name="image_quality_none">ᱪᱤᱛᱟᱹᱨ ᱵᱟᱝ ᱞᱚᱰ ᱢᱮ</string>
+    <string name="question_mark">\?</string>
+    <string name="share_playlist">ᱥᱮᱨᱮᱧ ᱛᱟᱹᱞᱠᱟᱹ ᱮᱢ</string>
+    <string name="share_playlist_with_titles">ᱧᱩᱛᱩᱢ ᱥᱟᱶ ᱮᱱᱮᱢ ᱢᱮ</string>
+    <string name="share_playlist_content_details">%1$s
+\n%2$s</string>
+    <string name="show_more">ᱟᱨᱦᱚᱸ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="show_less">ᱠᱚᱢ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="clear_queue_confirmation_title">ᱛᱟᱹᱞᱠᱟᱹ ᱪᱟᱪᱞᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱯᱟᱹᱛᱭᱟᱹᱣ ᱮᱢ ᱢᱮ</string>
+    <string name="clear_queue_confirmation_summary">ᱢᱤᱫ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟ. ᱠᱷᱚᱱ ᱮᱴᱟᱜ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟ. ᱛᱮ ᱵᱚᱫᱚᱞ ᱠᱟᱛᱮᱜ ᱟᱢᱟᱜ ᱠᱩᱠᱞᱤ ᱵᱚᱫᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="clear_queue_confirmation_description">ᱮᱠᱴᱤᱵᱽ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱩᱠᱞᱤ ᱫᱚ ᱵᱚᱫᱚᱞᱚᱜ-ᱟ</string>
+    <string name="show_description_title">ᱞᱟᱹᱱᱟᱹᱤ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="restore_defaults">ᱰᱤᱯᱷᱚᱞᱴ ᱠᱚ ᱨᱤᱯᱚᱴ ᱢᱮ</string>
+    <string name="progressive_load_interval_title">ᱯᱞᱮᱵᱮᱠ ᱞᱚᱰ ᱤᱱᱴᱟᱨᱵᱷᱮᱞ ᱨᱮᱱᱟᱜ ᱢᱟᱨᱟᱝ</string>
+    <string name="local">ᱞᱚᱠᱟᱞ</string>
+    <string name="account_terminated">ᱮᱠᱟᱩᱱᱴ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="get_notified">ᱵᱟᱰᱟᱭ ᱢᱮ</string>
+    <string name="ignore_hardware_media_buttons_title">ᱦᱟᱨᱰᱣᱮᱭᱟᱨ ᱢᱤᱰᱤᱭᱟ ᱵᱩᱴᱚᱱ ᱦᱮᱯᱨᱟᱣ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="downloads_storage_use_saf_title">System folder picker (SAF) ᱵᱮᱵᱷᱟᱨ ᱢᱮ</string>
+    <string name="default_video_format_title">ᱰᱤᱯᱷᱚᱞᱴ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱷᱚᱨᱢᱮᱴ</string>
+    <string name="popup_remember_size_pos_summary">ᱯᱚᱯ-ᱟᱯ ᱨᱮᱱᱟᱜ ᱢᱩᱪᱟᱹᱫ ᱢᱟᱨᱟᱝ ᱟᱨ ᱴᱷᱟᱶ ᱫᱚᱦᱚᱭ ᱢᱮ</string>
+    <string name="use_inexact_seek_title">ᱦᱩᱰᱤᱧ ᱵᱟᱹᱱᱩᱜ-ᱟ ᱞᱟᱦᱟᱱᱛᱤ ᱵᱮᱵᱷᱟᱨ</string>
+    <string name="use_inexact_seek_summary">ᱵᱟᱝ ᱥᱟᱹᱨᱤ ᱧᱮᱞ ᱠᱷᱟᱱ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱫᱚ ᱵᱟᱹᱲᱛᱤ ᱥᱟᱹᱠᱷᱭᱟᱹᱛ ᱥᱟᱶ ᱥᱟᱹᱠᱷᱭᱟᱹᱛ ᱠᱚ ᱵᱟᱰᱟᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱾ ᱕, ᱑᱕ ᱟᱨ ᱒᱕ ᱥᱮᱠᱮᱱᱰ ᱞᱟᱹᱜᱤᱫ ᱧᱮᱞᱚᱜᱼᱟ ᱱᱚᱶᱟ ᱥᱟᱶ ᱵᱟᱝ ᱠᱟᱹᱢᱤᱭᱟ</string>
+    <string name="show_comments_summary">ᱮᱱᱮᱢ ᱠᱚ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱱᱚᱶᱟ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="show_next_and_similar_title">\'Next\' ᱟᱨ \'Similar\' ᱵᱷᱤᱰᱤᱭᱳ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="show_meta_info_title">ᱢᱮᱴᱟ ᱴᱷᱟᱶ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="show_meta_info_summary">ᱥᱴᱨᱤᱢ ᱵᱮᱱᱟᱣᱤᱭᱟᱹ, ᱥᱴᱨᱤᱢ ᱟᱹᱛᱩ ᱟᱨᱵᱟᱝ ᱥᱟᱨᱪ ᱞᱟᱹᱠᱛᱤ ᱵᱟᱵᱚᱛ ᱟᱨᱦᱚᱸ ᱵᱟᱰᱟᱭ ᱞᱟᱹᱜᱤᱫ ᱢᱮᱴᱟ ᱤᱱᱯᱷᱳ ᱵᱚᱠᱥ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="auto_queue_title">ᱱᱮᱛᱟᱨᱟᱜ ᱥᱴᱨᱤᱢ ᱨᱮ ᱚᱴᱚᱼᱠᱤᱣ</string>
+    <string name="auto_queue_summary">ᱡᱚᱲᱟᱣ ᱟᱠᱟᱱ ᱥᱴᱨᱤᱢ ᱥᱮᱞᱮᱫ ᱠᱟᱛᱮ ᱪᱟᱪᱞᱟᱣ ᱪᱮᱛᱟᱱ (ᱱᱚᱱ-ᱨᱩᱯᱨᱤᱭᱩᱴᱤᱝ) ᱯᱞᱮᱼᱵᱷᱤᱠ ᱪᱮᱛᱟᱱ ᱪᱟᱪᱞᱟᱣ ᱢᱮ</string>
+    <string name="prefer_original_audio_summary">ᱯᱟᱹᱨᱥᱤ ᱵᱟᱝ ᱧᱮᱞ ᱠᱟᱛᱮᱜ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱚᱰᱤᱭᱚ ᱴᱨᱟᱠ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="prefer_descriptive_audio_title">ᱰᱤᱥᱠᱨᱤᱯᱴᱤᱵᱷ ᱚᱰᱤᱭᱚ ᱵᱟᱹᱲᱛᱤ</string>
+    <string name="auto_queue_toggle">ᱚᱴᱚ-ᱮᱱᱠᱭᱩᱤᱝ</string>
+    <string name="prefer_descriptive_audio_summary">ᱡᱩᱫᱤ ᱢᱮᱱᱟᱜ ᱟ ᱡᱩᱫᱤ ᱧᱮᱞ ᱵᱟᱹᱲᱤᱡ ᱦᱚᱲ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱢᱤᱫ ᱚᱰᱤᱭᱚ ᱴᱨᱟᱠ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="metadata_cache_wipe_complete_notice">ᱢᱮᱴᱟᱰᱮᱴᱟ ᱠᱮᱪ ᱵᱚᱫᱚᱞ</string>
+    <string name="left_gesture_control_title">ᱞᱤᱣᱮᱴ ᱡᱮᱥᱴ ᱮᱠᱥᱚᱱ</string>
+    <string name="right_gesture_control_summary">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱥᱠᱨᱤᱱ ᱨᱮᱱᱟᱜ ᱪᱮᱛᱟᱱ ᱨᱮ ᱪᱤᱱᱦᱟᱹ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="right_gesture_control_title">ᱞᱟᱹᱭ ᱪᱤᱠᱤ ᱠᱟᱹᱢᱤ</string>
+    <string name="start_main_player_fullscreen_summary">ᱢᱤᱱᱤ ᱯᱞᱮᱭᱟᱨ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱠᱚ ᱮᱛᱚᱦᱚᱵ ᱵᱟᱝ ᱢᱮ, ᱡᱩᱫᱤ ᱚᱴᱚᱯᱷᱤ ᱨᱚᱴᱮᱴ ᱞᱚᱠ ᱟᱠᱟᱱᱟ ᱮᱱᱠᱷᱟᱱ ᱥᱟᱹᱛ ᱥᱠᱨᱤᱱ ᱨᱮ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾ ᱟᱢ ᱢᱤᱱᱤ ᱯᱞᱮᱭᱟᱨ ᱪᱷᱟᱰᱟ ᱠᱟᱛᱮᱜ ᱦᱚᱸ ᱥᱮᱞᱮᱫ ᱫᱟᱲᱮᱭᱟᱜ ᱟ</string>
+    <string name="autoplay_title">ᱚᱴᱚᱯᱷᱟᱭ</string>
+    <string name="unsupported_url">ᱵᱟᱝ ᱥᱚᱦᱚᱫ ᱟᱠᱟᱱ URL</string>
+    <string name="unsupported_url_dialog_message">URL ᱵᱟᱭ ᱧᱟᱢ ᱫᱟᱲᱮᱭᱟᱫᱼᱟ. ᱮᱴᱟᱜ ᱟᱯᱞᱤᱠᱮᱥᱚᱱ ᱛᱮ ᱮᱛᱚᱦᱚᱵ ᱢᱮ?</string>
+    <string name="brightness">ᱡᱤᱞᱤᱧ</string>
+    <string name="show_search_suggestions_title">ᱛᱚᱨᱡᱚᱢᱟ ᱥᱟᱯᱲᱟᱣ</string>
+    <string name="show_search_suggestions_summary">ᱧᱮᱞ ᱛᱟᱵᱩᱱ ᱯᱮ ᱺ</string>
+    <string name="remote_search_suggestions">ᱨᱮᱢᱳᱴ ᱥᱟᱨᱪ ᱥᱩᱯᱨᱮᱥ</string>
+    <string name="enable_watch_history_summary">ᱧᱮᱞ ᱟᱠᱟᱱ ᱵᱷᱤᱰᱤᱭᱳ ᱠᱚ ᱪᱮᱛᱟᱱ ᱨᱮ ᱫᱚᱦᱚᱭ ᱢᱮ</string>
+    <string name="resume_on_audio_focus_gain_summary">ᱡᱟᱦᱟᱱᱟᱜ ᱵᱚᱫᱚᱞ ᱛᱟᱭᱚᱢ ᱠᱷᱮᱞ ᱫᱚᱦᱚᱭ ᱢᱮ (ᱡᱮᱞᱮᱠᱟ: ᱯᱷᱚᱱ ᱠᱚᱞ)</string>
+    <string name="download_dialog_title">ᱰᱟᱩᱱᱞᱚᱰ ᱢᱮ</string>
+    <string name="start_main_player_fullscreen_title">ᱢᱩᱬᱩᱛ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱡᱚᱛᱚ ᱪᱤᱛᱟᱹᱨ ᱨᱮ ᱮᱛᱚᱦᱚᱵ ᱢᱮ</string>
+    <string name="content_language_title">ᱰᱤᱯᱷᱚᱞᱴ ᱠᱚᱱᱴᱮᱱᱴ ᱯᱟᱹᱨᱥᱤ</string>
+    <string name="peertube_instance_url_title">PeerTube ᱚᱞ ᱠᱚ</string>
+    <string name="peertube_instance_url_summary">ᱟᱢᱟᱜ ᱧᱩᱛᱩᱢᱟᱱ PeerTube ᱵᱷᱤᱛᱨᱤ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="peertube_instance_add_fail">ᱞᱟᱹᱱᱟᱹᱤ ᱫᱚᱦᱚ ᱫᱟᱲᱮᱭᱟᱜ ᱵᱟᱝ</string>
+    <string name="settings_category_video_audio_title">ᱵᱷᱤᱰᱤᱭᱳ ᱟᱨ ᱚᱰᱤᱭᱳ</string>
+    <string name="peertube_instance_add_https_only">HTTPS URL ᱠᱚ ᱜᱮ ᱥᱚᱯᱷᱴᱣᱮᱨ ᱟᱠᱟᱱᱟ</string>
+    <string name="peertube_instance_add_exists">ᱤᱱᱥᱴᱮᱱᱥ ᱫᱚ ᱟᱞᱮ ᱢᱮᱱᱟᱜᱼᱟ</string>
+    <string name="settings_category_player_behavior_title">ᱟᱹᱱᱟᱹᱨᱤ</string>
+    <string name="settings_category_player_notification_summary">ᱚᱛᱱᱚᱜ ᱪᱤᱛᱟᱹᱨ ᱠᱷᱚᱸᱫᱽᱨᱚᱸᱫᱽ ᱥᱟᱯᱲᱟᱣ</string>
+    <string name="show_age_restricted_content_summary">ᱜᱤᱫᱨᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱝ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱠᱟᱛᱷᱟ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ ᱪᱮᱫᱟᱜ ᱥᱮ ᱱᱚᱣᱟ ᱨᱮ ᱢᱤᱫ ᱩᱢᱮᱨ ᱞᱤᱢᱤᱴ ᱢᱮᱱᱟᱜᱼᱟ (ᱡᱮᱞᱮᱠᱟ ᱑᱘+)</string>
+    <string name="youtube_restricted_mode_enabled_summary">ᱭᱩᱴᱭᱩᱵᱽ ᱢᱤᱫ \"Restricted Mode\" ᱮᱢᱚᱜᱼᱟ ᱡᱟᱦᱟᱸ ᱫᱚ ᱯᱚᱴᱮᱱᱥᱤᱭᱟᱞ ᱢᱚᱰᱩᱞ ᱠᱚᱱᱴᱮᱱᱴ (mature content) ᱫᱚᱦᱚᱭᱟ</string>
+    <string name="restricted_video_no_stream">ᱱᱤᱭᱟᱹ ᱵᱷᱤᱰᱤᱭᱳ ᱫᱚ ᱩᱢᱚᱨ ᱞᱟᱹᱜᱤᱫ ᱜᱮ᱾_x000D_
+\nᱭᱩᱴᱭᱩᱵᱽ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱨᱮᱱᱟᱜ ᱩᱢᱮᱨ ᱫᱚᱦᱚ ᱞᱟᱹᱜᱤᱫ ᱱᱟᱣᱟ ᱯᱳᱞᱤᱥᱤ ᱠᱷᱟᱹᱛᱤᱨ, ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯᱷ ᱫᱚ ᱟᱡᱟᱜ ᱵᱷᱤᱰᱤᱭᱳ ᱥᱴᱨᱤᱢ ᱨᱮᱱᱟᱜ ᱡᱟᱦᱟᱸᱭ ᱦᱚᱸ ᱥᱮᱴᱮᱨ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱵᱟᱝ ᱟᱨ ᱚᱱᱟᱛᱮ ᱱᱚᱶᱟ ᱯᱞᱮᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ᱾</string>
+    <string name="duration_live">ᱡᱤᱣᱤ</string>
+    <string name="playlists">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱚ</string>
+    <string name="videos_string">ᱵᱷᱤᱰᱤᱭᱳ</string>
+    <string name="users">ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ</string>
+    <string name="events">ᱜᱷᱚᱴᱚᱱ ᱠᱚ</string>
+    <string name="app_update_notification_channel_name">ᱮᱯ ᱟᱯᱰᱮᱴ ᱱᱳᱴᱤᱯᱮᱥᱚᱱ</string>
+    <string name="app_update_notification_channel_description">ᱱᱟᱣᱟ ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯ ᱵᱟᱨᱥᱚᱱ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱚᱵᱚᱨ</string>
+    <string name="error_report_title">ᱪᱷᱟᱸᱪ ᱠᱚ</string>
+    <string name="hash_channel_name">ᱵᱷᱤᱰᱤᱭᱳ ᱦᱟᱥ ᱱᱮᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ</string>
+    <string name="error_report_channel_name">ᱪᱟᱹᱠᱨᱤ ᱠᱷᱚᱵᱚᱨ ᱠᱷᱚᱵᱚᱨ</string>
+    <string name="switch_to_background">ᱯᱷᱚᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ</string>
+    <string name="switch_to_popup">ᱯᱚᱯ-ᱟᱯ ᱨᱮ ᱥᱮᱞᱮᱫ ᱢᱮ</string>
+    <string name="switch_to_main">ᱢᱩᱬᱩᱛ ᱨᱮ ᱞᱤᱱ ᱢᱮ</string>
+    <string name="export_data_title">ᱮᱠᱥᱯᱳᱨᱴ ᱰᱮᱴᱟᱵᱮᱥ</string>
+    <string name="import_data_summary">ᱟᱢᱟᱜ ᱱᱮᱛᱟᱨᱟᱜ ᱱᱟᱜᱟᱢ, ᱥᱟᱵᱽᱥᱠᱨᱟᱭᱵᱽᱥ, ᱯᱷᱟᱭᱞᱤᱥᱴ ᱟᱨ (ᱵᱟᱪᱷᱱᱟᱣ ᱞᱮᱠᱟᱛᱮ) ᱥᱤᱴᱤᱝ ᱠᱚ ᱩᱪᱟᱹᱲᱟ</string>
+    <string name="clear_cookie_summary">ᱟᱢ ᱢᱤᱫ reCAPTCHA ᱥᱟᱯᱲᱟᱣ ᱞᱮᱠᱷᱟᱱ NewPipe ᱥᱟᱦᱴᱟᱨᱮ ᱥᱚᱫᱚᱨ ᱟᱠᱟᱱ ᱠᱩᱠᱤ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="clear_playback_states_title">ᱯᱷᱟᱭᱞᱟᱣ ᱴᱷᱟᱶ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="clear_playback_states_summary">ᱡᱚᱛᱚ ᱯᱞᱮᱤᱯᱷᱟᱭᱤᱰ ᱯᱚᱡᱤᱥᱚᱱ ᱠᱚ ᱵᱚᱫᱚᱞᱟ</string>
+    <string name="delete_playback_states_alert">ᱡᱚᱛᱚ ᱯᱞᱮᱵᱟᱠ ᱯᱳᱡᱤᱥᱚᱱ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="watch_history_states_deleted">ᱠᱷᱮᱞᱚᱸᱰ ᱨᱮᱱᱟᱜ ᱴᱷᱟᱶ ᱠᱚ ᱪᱷᱩᱴᱟᱹᱣ ᱟᱠᱟᱱᱟ</string>
+    <string name="clear_search_history_title">ᱥᱩᱪᱚᱱᱟ ᱨᱮᱭᱟᱜ ᱱᱟᱜᱟᱢ ᱪᱷᱟᱯᱟ ᱢᱮ</string>
+    <string name="clear_search_history_summary">ᱪᱷᱟᱸᱪ:Search keywords ᱨᱮᱱᱟᱜ ᱱᱟᱜᱟᱢ ᱪᱷᱟᱸᱪᱟᱣᱟ</string>
+    <string name="delete_search_history_alert">ᱥᱟᱱᱟᱢ ᱥᱩᱪᱚᱱᱟ ᱱᱟᱜᱟᱢ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="could_not_setup_download_menu">ᱰᱟᱩᱱᱞᱚᱰ ᱢᱮᱱᱩ ᱵᱟᱭ ᱥᱮᱯᱴᱮᱭᱟ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="app_ui_crash">ᱮᱯᱞᱤᱠᱮᱥᱚᱱ/UI ᱠᱨᱟᱥ ᱞᱮᱱᱟ</string>
+    <string name="player_stream_failure">ᱱᱚᱣᱟ ᱥᱴᱨᱤᱢ ᱯᱞᱮ ᱵᱟᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="player_unrecoverable_failure">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱵᱟᱭ ᱵᱩᱡᱷᱟᱹᱣ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
+    <string name="player_recoverable_failure">ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱦᱩᱫᱤᱥ ᱠᱷᱚᱱ ᱨᱤᱠᱳᱨᱰ</string>
+    <string name="invalid_file">ᱱᱚᱶᱟ ᱯᱷᱤᱞ ᱵᱟᱹᱱᱩᱜᱼᱟ ᱥᱮ ᱱᱚᱶᱟ ᱨᱮ ᱚᱞ ᱟᱨ ᱯᱟᱲᱦᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱫᱟᱣ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="file_name_empty_error">ᱯᱷᱤᱞᱤᱢ ᱧᱩᱛᱩᱢ ᱵᱚᱸᱫᱚ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱵᱟᱝ</string>
+    <string name="error_occurred_detail">%1$s ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱞᱮᱱᱟ:</string>
+    <string name="no_streams_available_download">ᱰᱟᱩᱱᱞᱚᱰ ᱞᱟᱹᱜᱤᱫ ᱡᱟᱦᱟᱱ ᱥᱴᱨᱤᱢ ᱵᱟᱭ</string>
+    <string name="saved_tabs_invalid_json">ᱥᱚᱞᱦᱮ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱵᱟᱝ ᱯᱟᱲᱦᱟᱣ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ, ᱚᱱᱟᱛᱮ ᱵᱮᱵᱷᱟᱨᱤᱡᱽ ᱠᱚ</string>
+    <string name="restore_defaults_confirmation">ᱟᱢ ᱥᱩᱯᱚᱨᱴ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮᱢᱮ?</string>
+    <string name="permission_display_over_apps">ᱮᱴᱟᱜ ᱮᱯ ᱨᱮ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱫᱟᱣ ᱮᱢ ᱢᱮ</string>
+    <string name="error_report_notification_title">NewPipe ᱨᱮ ᱢᱤᱫᱴᱟᱹᱝ ᱵᱷᱩᱞ ᱧᱟᱢ ᱞᱮᱱᱟ, ᱠᱷᱚᱵᱚᱨ ᱞᱟᱹᱜᱤᱫ ᱴᱟᱯ ᱢᱮ</string>
+    <string name="sorry_string">ᱤᱧᱤᱧ ᱵᱟᱹᱧ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ, ᱱᱚᱶᱟ ᱫᱚ ᱵᱟᱝ ᱦᱩᱭᱩᱜ ᱞᱟᱹᱠᱛᱤ ᱛᱟᱦᱮᱸᱫ ᱾</string>
+    <string name="error_report_button_text">ᱤ-ᱢᱮᱞ ᱤᱫᱤ ᱠᱟᱛᱮ ᱨᱤᱯᱚᱨᱴ ᱢᱮ</string>
+    <string name="copy_for_github">ᱯᱷᱚᱨᱢᱮᱴ ᱟᱠᱟᱱ ᱨᱤᱯᱳᱨᱴ ᱠᱚᱯᱤ ᱢᱮ</string>
+    <string name="error_report_open_github_notice">ᱟᱢᱟᱜ ᱠᱨᱮᱥᱴ ᱵᱟᱵᱚᱫᱽ ᱛᱮ ᱢᱤᱫ ᱚᱱᱚᱞ ᱢᱮᱱᱟᱜ ᱟ ᱥᱮ ᱵᱟᱝ ᱚᱱᱟ ᱧᱮᱞ ᱢᱮ ᱾ ᱰᱩᱯᱞᱤᱠᱮᱴ ᱴᱤᱠᱮᱴ ᱵᱮᱱᱟᱣ ᱞᱮᱠᱷᱟᱱ, ᱟᱢ ᱫᱚ ᱚᱠᱛᱚ ᱟᱢᱮᱢ ᱦᱟᱛᱟᱣᱮᱫᱟ ᱡᱟᱦᱟᱸ ᱫᱚ ᱟᱥᱚᱞ ᱵᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱨᱮ ᱟᱢᱮᱢ ᱦᱟᱛᱟᱣ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱾</string>
+    <string name="what_device_headline">ᱤᱱᱯᱷᱳ:</string>
+    <string name="what_happened_headline">ᱪᱮᱫ ᱦᱩᱭ ᱞᱮᱱᱟ:</string>
+    <string name="error_details_headline">ᱞᱟᱹᱱᱟᱹᱤ:</string>
+    <string name="detail_thumbnail_view_description">ᱵᱷᱤᱰᱤᱭᱚ ᱯᱞᱮ, ᱚᱠᱛᱚ:</string>
+    <string name="detail_dislikes_img_view_description">ᱵᱟᱹᱲᱤᱡ</string>
+    <string name="comments_tab_description">ᱠᱚᱢᱮᱱᱴ</string>
+    <string name="related_items_tab_description">ᱥᱟᱶᱦᱮᱫ ᱮᱞᱠᱷᱟ</string>
+    <string name="description_tab_description">ᱞᱟᱹᱱᱟᱹᱤ</string>
+    <string name="import_subscriptions_hint">᱓ ᱰᱳᱴ ᱢᱮᱱᱩ ᱠᱷᱚᱱ ᱤᱢᱯᱳᱨᱴ ᱟᱨ ᱮᱠᱥᱯᱳᱨᱴ ᱥᱟᱵᱥᱠᱨᱟᱭᱵᱮᱥᱚᱱ</string>
+    <string name="detail_drag_description">ᱱᱟᱣᱟ ᱟᱹᱨᱡᱤ ᱞᱟᱹᱜᱤᱫ ᱟᱹᱪᱩᱨ ᱢᱮ</string>
+    <string name="audio">ᱚᱰᱤᱭᱳ</string>
+    <string name="retry">ᱟᱨᱦᱚᱸ ᱯᱟᱲᱦᱟᱣ ᱢᱮ</string>
+    <string name="short_billion">ᱵᱤ</string>
+    <string name="drawer_header_description">ᱱᱤᱛᱚᱜ ᱵᱟᱪᱷᱟᱣ ᱟᱠᱟᱱ ᱴᱳᱜᱞ ᱥᱮᱵᱟ:</string>
+    <string name="no_subscribers">ᱚᱵᱷᱤᱱᱮᱛᱟᱨ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="no_one_watching">ᱚᱠᱚᱭ ᱦᱚᱸ ᱵᱟᱝ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="done">ᱜᱚᱴᱟ ᱟᱠᱟᱱᱟ</string>
+    <string name="title_activity_about">ᱱᱤᱭᱩ ᱯᱟᱭᱯᱮ ᱵᱟᱵᱚᱛ</string>
+    <string name="title_licenses">ᱯᱮᱨᱟᱜ ᱞᱟᱭᱥᱮᱱᱥ</string>
+    <string name="donation_title">ᱰᱳᱱᱮᱴ ᱢᱮ</string>
+    <string name="no_one_listening">ᱡᱟᱦᱟᱸᱭ ᱜᱮᱭ ᱟᱸᱡᱚᱢᱟ</string>
+    <string name="infinite_videos">∞ ᱵᱷᱤᱰᱤᱭᱳ</string>
+    <string name="no_comments">ᱵᱟᱦᱟ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="comments_are_disabled">ᱠᱚᱢᱮᱱᱴ ᱫᱚ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="start">ᱮᱛᱚᱦᱚᱵ</string>
+    <string name="pause">ᱯᱷᱟᱭᱩᱥ</string>
+    <string name="create">ᱵᱮᱱᱟᱣ ᱢᱮ</string>
+    <string name="no_available_dir">ᱛᱷᱚᱠ ᱨᱮ ᱛᱟᱭᱚᱢ ᱛᱮ ᱢᱤᱫ ᱰᱟᱩᱱᱞᱚᱰ ᱯᱷᱳᱞᱰᱟᱨ ᱮᱢ ᱢᱮ</string>
+    <string name="one_item_deleted">ᱢᱤᱫᱴᱟᱹᱝ ᱡᱤᱱᱤᱥ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="recaptcha_request_toast">reCAPTCHA challenge ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ</string>
+    <string name="recaptcha_solve">ᱥᱚᱞᱦᱮ</string>
+    <string name="donation_encouragement">ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯ ᱫᱚ ᱱᱤᱡᱮᱨᱟᱜ ᱚᱠᱛᱚ ᱟᱢᱟᱜ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱵᱷᱟᱹᱜᱤ ᱵᱷᱟᱹᱜᱤ ᱥᱟᱹᱜᱟᱹᱭ ᱮᱢ ᱠᱟᱛᱮ ᱵᱮᱱᱟᱣ ᱟᱠᱟᱱᱟ ᱾ ᱱᱤᱣ ᱯᱟᱭᱤᱯ ᱵᱮᱨᱮᱫ ᱞᱟᱹᱜᱤᱫ ᱮᱱᱮᱢᱤᱭᱟᱹ ᱠᱚ ᱜᱚᱲᱚ ᱥᱚᱦᱚᱫ ᱮᱢ ᱢᱮ ᱾</string>
+    <string name="website_title">ᱣᱮᱵᱽᱥᱟᱭᱤᱴ</string>
+    <string name="app_license_title">ᱱᱤᱣ ᱯᱟᱭᱯ ᱨᱮᱭᱟᱜ ᱞᱟᱭᱥᱮᱱᱥ</string>
+    <string name="app_license">ᱱᱤᱭᱩ ᱯᱟᱭᱯ ᱫᱚ ᱠᱚᱯᱤᱞᱮᱠᱴ ᱞᱤᱵᱨᱮ ᱥᱚᱯᱷᱴᱣᱮᱭᱟᱨ ᱠᱟᱱᱟ ᱾ ᱟᱢ ᱱᱚᱶᱟ ᱵᱮᱵᱷᱟᱨ, ᱯᱟᱲᱦᱟᱣ, ᱮᱴᱟᱜ ᱦᱚᱲ ᱥᱟᱶ ᱮᱢ ᱟᱨ ᱵᱮᱵᱚᱥᱛᱟ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾ ᱟᱢ ᱱᱚᱶᱟ GNU General Public License ᱞᱮᱠᱟᱛᱮ ᱯᱟᱨᱥᱟᱞ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱟᱨ/ᱟᱨᱵᱟᱝ Free Software Foundation ᱦᱚᱛᱮᱛᱮ ᱵᱮᱵᱦᱟᱨ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ, ᱡᱟᱦᱟᱸ ᱫᱚ ᱞᱟᱭᱥᱮᱱᱥ ᱨᱮᱱᱟᱜ ᱵᱟᱨᱥᱚᱱ ᱓ ᱥᱮ (ᱛᱟᱢᱟᱜ ᱵᱟᱪᱷᱟᱣ ᱞᱮᱠᱟᱛᱮ) ᱡᱟᱦᱟᱸᱱᱟᱜ ᱛᱟᱭᱚᱢ ᱵᱟᱨᱥᱚᱱ ᱠᱟᱱᱟ ᱾</string>
+    <string name="website_encouragement">ᱟᱨᱦᱚᱸ ᱵᱟᱰᱟᱭ ᱟᱨ ᱠᱷᱚᱵᱚᱨ ᱞᱟᱹᱜᱤᱫ NewPipe ᱣᱮᱵᱽᱥᱟᱭᱤᱴ ᱧᱮᱞ ᱢᱮ ᱾</string>
+    <string name="faq_description">ᱡᱩᱫᱤ ᱟᱢ ᱱᱚᱶᱟ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱵᱮᱵᱚᱦᱟᱨ ᱨᱮ ᱠᱷᱟᱡᱽᱱᱟ ᱮ ᱧᱟᱢᱟ, ᱮᱱᱠᱷᱟᱱ ᱱᱚᱶᱟ ᱥᱟᱸᱣ ᱡᱩᱲᱟᱹᱣ ᱟᱠᱟᱱ ᱯᱩᱥᱴᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ!</string>
+    <string name="faq">ᱣᱮᱵᱽᱥᱟᱭᱤᱴ ᱨᱮ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="title_activity_history">ᱱᱟᱜᱟᱢ</string>
+    <string name="action_history">ᱱᱟᱜᱟᱢ</string>
+    <string name="delete_item_search_history">ᱟᱢ ᱱᱚᱶᱟ ᱡᱤᱱᱤᱥ ᱥᱟᱸᱪᱟᱨ ᱱᱟᱜᱟᱢ ᱠᱷᱚᱱ ᱵᱚᱫᱚᱞ ᱢᱮᱢᱮ?</string>
+    <string name="title_last_played">ᱢᱩᱪᱟᱹᱫ ᱠᱷᱮᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="title_most_played">ᱡᱟᱹᱥᱛᱤ ᱠᱷᱮᱞ ᱟᱠᱟᱱ</string>
+    <string name="main_page_content">ᱢᱩᱬᱩᱛ ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱥᱟᱦᱴᱟ</string>
+    <string name="main_page_content_summary">ᱢᱩᱬᱩᱛ ᱥᱟᱦᱴᱟ ᱨᱮ ᱚᱠᱟ ᱛᱟᱵᱽ ᱠᱚ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="main_page_content_swipe_remove">ᱡᱤᱱᱤᱥ ᱠᱚ ᱵᱟᱧᱪᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱥᱣᱟᱭᱯ ᱢᱮ</string>
+    <string name="default_kiosk_page_summary">ᱰᱤᱯᱷᱚᱞᱴ ᱠᱤᱭᱳᱥᱠ</string>
+    <string name="kiosk_page_summary">ᱠᱤᱭᱳᱥᱠ ᱥᱟᱦᱴᱟ</string>
+    <string name="channel_page_summary">ᱪᱟᱱᱮᱞ ᱥᱟᱦᱴᱟ</string>
+    <string name="select_a_channel">ᱪᱟᱱᱮᱞ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="no_channel_subscribed_yet">ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡ ᱪᱮᱱᱮᱞ ᱥᱚᱵᱽᱥᱠᱨᱟᱭᱵᱽ ᱵᱟᱝ</string>
+    <string name="select_a_playlist">ᱢᱤᱫ ᱯᱷᱟᱤᱞᱤᱥᱴ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="import_complete_toast">ᱤᱢᱯᱳᱨᱴ</string>
+    <string name="no_playlist_bookmarked_yet">ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡ playlist bookmarks ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="localization_changes_requires_app_restart">ᱮᱯ ᱮᱦᱚᱵ ᱞᱮᱠᱷᱟᱱ ᱯᱟᱹᱨᱥᱤ ᱵᱚᱫᱚᱞᱚᱜ-ᱟ</string>
+    <string name="export_complete_toast">ᱮᱥᱯᱟᱨᱴ ᱟᱠᱟᱱᱟ</string>
+    <string name="trending">ᱴᱨᱮᱱᱰᱤᱝ</string>
+    <string name="play_queue_audio_settings">ᱚᱰᱤᱭᱳ ᱥᱮᱴᱤᱝ</string>
+    <string name="play_queue_audio_track">ᱚᱰᱤᱭᱳ: %s</string>
+    <string name="loading_stream_details">ᱥᱴᱨᱤᱢ ᱨᱮᱱᱟᱜ ᱰᱮᱴᱮᱞ ᱞᱚᱰ ᱠᱟᱱᱟ…</string>
+    <string name="drawer_close">ᱥᱩᱯᱚᱨ ᱰᱨᱟᱵᱷᱤᱴ</string>
+    <string name="create_playlist">ᱱᱟᱶᱟ ᱯᱞᱮᱭᱞᱤᱥᱴ</string>
+    <string name="mute">ᱢᱩᱴ</string>
+    <string name="start_here_on_popup">ᱢᱤᱫ ᱯᱚᱯ-ᱟᱯ ᱨᱮ ᱠᱷᱮᱞᱚᱸᱰ ᱮᱛᱚᱦᱚᱵ ᱢᱮ</string>
+    <string name="set_as_playlist_thumbnail">ᱯᱷᱟᱤᱞᱤᱯᱷᱟᱤᱞᱤ ᱞᱮᱠᱟᱛᱮ ᱥᱮᱞᱮᱫ ᱢᱮ</string>
+    <string name="bookmark_playlist">ᱵᱩᱠᱢᱟᱨᱠ ᱯᱞᱮᱭᱞᱤᱥᱴ</string>
+    <string name="caption_setting_title">ᱪᱷᱟᱸᱪ</string>
+    <string name="enable_disposed_exceptions_summary">ᱡᱤᱵᱚᱱ ᱪᱟᱞᱟᱣ ᱛᱟᱭᱚᱢ ᱡᱟᱹᱛᱤ ᱥᱮ ᱠᱟᱹᱢᱤ ᱡᱤᱭᱚᱱ ᱪᱮᱛᱟᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱟᱝ ᱦᱟᱹᱴᱤᱧ ᱫᱟᱲᱮᱭᱟᱜ Rx ᱮᱥᱠᱮᱪᱥᱚᱱ ᱨᱮᱱᱟᱜ ᱯᱷᱚᱨᱥ ᱨᱮᱯᱚᱨᱴᱤᱝ</string>
+    <string name="unbookmark_playlist">ᱵᱩᱠᱢᱟᱨᱠ ᱚᱪᱚᱭ ᱢᱮ</string>
+    <string name="delete_playlist_prompt">ᱱᱚᱶᱟ ᱯᱷᱟᱭᱞᱤᱥᱴ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="caption_auto_generated">ᱚᱴᱚ-ᱡᱮᱱᱮᱨᱮᱴ</string>
+    <string name="show_original_time_ago_title">ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="show_original_time_ago_summary">ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ</string>
+    <string name="disable_media_tunneling_summary">ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾</string>
+    <string name="show_image_indicators_title">ᱪᱤᱛᱟᱹᱨ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="check_new_streams">ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ</string>
+    <string name="create_error_notification">ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ</string>
+    <string name="subscriptions_export_unsuccessful">ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="import_youtube_instructions">\"ᱜᱩᱜᱩᱞ ᱴᱮᱠᱟᱣᱩᱴ ᱠᱷᱚᱱ ᱭᱩᱴᱭᱩᱵᱽ ᱥᱚᱵᱽᱥᱠᱨᱟᱭᱵᱽᱥ ᱤᱱᱯᱳᱨᱴ ᱢᱮ:
+\n
+\n1. ᱱᱚᱶᱟ URL ᱨᱮ ᱪᱟᱞᱟᱜ ᱢᱮ: %1$s
+\n2. ᱞᱚᱜᱤᱱ ᱢᱮ ᱡᱚᱠᱷᱚᱱ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ
+\n3. \"\'All data included\"\' ᱨᱮ ᱞᱤᱱ ᱢᱮ, ᱛᱟᱭᱚᱢ \"\'Deselect all\"\' ᱨᱮ ᱞᱤᱱ ᱢᱮ, ᱛᱟᱭᱚᱢ \"\'subscriptions\"\' ᱨᱮ ᱞᱤᱱ ᱢᱮ ᱟᱨ \"\'OK\"\' ᱨᱮ ᱞᱤᱱ ᱢᱮ
+\n4. \"Next step\" ᱨᱮ ᱟᱨ ᱚᱱᱟ ᱛᱟᱭᱚᱢ \"Create export\" ᱨᱮ ᱞᱤᱱ ᱢᱮ
+\n5. ᱱᱚᱶᱟ ᱧᱮᱞ ᱛᱟᱭᱚᱢ \"\"Download\"\" ᱵᱩᱴᱚᱱ ᱨᱮ ᱠᱞᱤᱠ ᱢᱮ
+\n6. ᱞᱟᱛᱟᱨ ᱨᱮ IMPORT FILE ᱨᱮ ᱞᱤᱱ ᱢᱮ ᱟᱨ ᱰᱟᱩᱱᱞᱚᱰ ᱟᱠᱟᱱ . zip ᱯᱷᱤᱞ ᱵᱟᱪᱷᱟᱣ ᱢᱮ
+\n7. ᱡᱩᱫᱤ .zip ᱤᱱᱯᱷᱚᱨᱴ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱵᱟᱝ ᱠᱟᱱᱟ ᱮᱱᱠᱷᱟᱱ .csv ᱯᱷᱤᱞ ᱮᱠᱥᱴᱨᱟᱠᱴ ᱢᱮ (\"ᱭᱩᱴᱭᱩᱵᱽ ᱟᱨ ᱭᱩᱴᱭᱩᱵᱽ ᱢᱤᱣᱡᱤᱠ/ᱥᱵᱽᱥᱠᱨᱟᱭᱵᱮᱥᱚᱱ/ᱥᱵᱽᱥᱠᱨᱟᱭᱵᱮᱥᱚᱱ.ᱥᱤᱵᱷ\" ᱨᱮ), ᱞᱟᱛᱟᱨ ᱨᱮ IMPORT FILE ᱨᱮ ᱠᱞᱤᱠ ᱢᱮ ᱟᱨ ᱮᱠᱥᱴᱨᱟᱠᱴ ᱟᱠᱟᱱ csv ᱯᱷᱤᱞ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="import_soundcloud_instructions_hint">yourID, soundcloud.com/yourid</string>
+    <string name="start_accept_privacy_policy">\"ᱤᱭᱩᱨᱚᱯᱤᱭᱚ ᱡᱚᱡᱚᱱᱟ ᱯᱨᱚᱫᱚᱭᱚᱜᱤᱠᱤ (ᱡᱤ.ᱰᱤ.ᱯᱤ.ᱟᱨ.) ᱥᱟᱞᱟᱜ ᱥᱚᱢᱵᱚᱸᱫᱷ ᱞᱟᱹᱜᱤᱫ ᱛᱮ, ᱱᱚᱣᱟ ᱦᱚᱛᱮᱛᱮ ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯ ᱨᱮᱭᱟᱜ ᱯᱨᱟᱭᱵᱷᱮᱴ ᱯᱚᱞᱤᱥᱤ ᱨᱮ ᱟᱵᱚᱣᱟᱜ ᱟᱫᱷᱤᱱᱤᱭᱚᱢ ᱠᱚ ᱩᱫᱩᱜ ᱦᱚᱪᱚᱭᱟ। ᱱᱚᱶᱟ ᱫᱚ ᱞᱟᱹᱭᱛᱮ ᱯᱟᱲᱦᱟᱣ ᱢᱮ ᱾
+\nᱟᱢ ᱫᱚ ᱱᱚᱶᱟ ᱯᱟᱹᱛᱭᱟᱹᱣ ᱢᱮ ᱡᱮ ᱟᱢ ᱵᱷᱤᱜᱽ ᱨᱮᱯᱳᱨᱴ ᱮᱢ ᱢᱮ ᱾\"</string>
+    <string name="enable_streams_notifications_summary">ᱥᱚᱵᱥᱠᱨᱟᱭᱵᱮᱥᱚᱱ ᱠᱷᱚᱱ ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱠᱚ ᱵᱟᱰᱟᱭ ᱢᱮ</string>
+    <string name="updates_setting_title">ᱚᱯᱰᱮᱴᱥ</string>
+    <string name="updates_setting_description">ᱡᱚᱠᱷᱚᱱ ᱱᱟᱣᱟ ᱵᱟᱨᱥᱚᱱ ᱢᱮᱱᱟᱜ-ᱟ ᱮᱯ ᱟᱯᱰᱮᱴ ᱞᱟᱹᱜᱤᱫ ᱯᱟᱹᱛᱭᱟᱹᱣ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="minimize_on_exit_popup_description">ᱯᱚᱯ-ᱟᱯ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱞᱟᱹᱜᱤᱫ ᱞᱟᱹᱴᱩ ᱦᱚᱪᱚ</string>
+    <string name="autoplay_summary">ᱚᱞᱪᱤᱠᱤ ᱛᱮ ᱨᱩᱭᱟᱹᱲᱚᱜ ᱢᱮ - %s</string>
+    <string name="list_view_mode">ᱞᱤᱥᱴᱤ ᱧᱮᱧᱮᱞ ᱢᱚᱰ</string>
+    <string name="list">ᱞᱤᱥᱴᱤ</string>
+    <string name="auto">ᱚᱴᱚ</string>
+    <string name="app_update_available_notification_title">ᱱᱟᱣᱟ ᱯᱟᱭᱤᱯ ᱚᱯᱰᱮᱴ ᱢᱮᱱᱟᱜ-ᱟ!</string>
+    <string name="app_update_available_notification_text">%s ᱠᱚᱰ ᱦᱚᱪᱚ ᱞᱟᱹᱜᱤᱫ ᱴᱟᱯ ᱢᱮ</string>
+    <string name="missions_header_finished">ᱢᱩᱪᱟᱹᱫ</string>
+    <string name="checking_updates_toast">ᱱᱤᱛᱚᱜᱟᱜ ᱠᱷᱚᱵᱚᱨ ᱠᱚ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ…</string>
+    <string name="download_failed">ᱰᱟᱩᱱᱞᱚᱰ ᱵᱟᱹᱱᱩᱜ-ᱟ</string>
+    <string name="generate_unique_name">ᱧᱩᱛᱩᱢ ᱛᱮᱭᱟᱨ ᱢᱮ</string>
+    <string name="overwrite">ᱩᱫᱩᱜ ᱢᱮ</string>
+    <string name="show_error">ᱪᱷᱟᱸᱪ:Fact</string>
+    <string name="error_file_creation">ᱯᱷᱤᱞ ᱵᱮᱱᱟᱣ ᱫᱚ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ</string>
+    <string name="error_unknown_host">ᱥᱮᱵᱟ ᱧᱟᱢ ᱵᱟᱝ ᱜᱟᱱᱚᱜ ᱠᱟᱱᱟ</string>
+    <string name="error_http_no_content">ᱥᱮᱵᱟ ᱫᱚ ᱚᱱᱚᱞ ᱵᱟᱝ ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="error_http_unsupported_range">ᱥᱟᱨᱵᱟᱨ ᱫᱚ ᱢᱚᱞᱴᱤ-ᱛᱷᱨᱮᱰ ᱰᱟᱩᱱᱞᱚᱰ ᱠᱚ ᱵᱟᱝ ᱮᱢᱚᱜ ᱠᱟᱱᱟ, @string/msg_threads = 1 ᱥᱟᱶᱛᱮ ᱮᱱᱮᱡ ᱢᱮ</string>
+    <string name="error_postprocessing_failed">ᱛᱟᱭᱚᱢ ᱛᱮᱭᱟᱨ ᱵᱟᱝ ᱪᱟᱞᱟᱣ ᱟᱠᱟᱱᱟ</string>
+    <string name="error_postprocessing_stopped">ᱱᱩᱯᱯᱟᱭᱤᱯᱷ ᱯᱷᱤᱞ ᱨᱮ ᱠᱟᱹᱢᱤ ᱚᱠᱛᱚ ᱨᱮ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="error_progress_lost">ᱯᱟᱹᱛᱭᱟᱹᱣ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ, ᱪᱮᱫᱟᱜ ᱥᱮ ᱯᱷᱤᱞᱤᱯᱟᱭᱤᱰ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="error_timeout">ᱡᱚᱱᱚᱲ ᱚᱠᱛᱚ</string>
+    <string name="confirm_prompt">ᱟᱢ ᱟᱢᱟᱜ ᱰᱟᱩᱱᱞᱚᱰ ᱦᱤᱥᱛᱟ ᱵᱚᱫᱚᱞ ᱢᱮ ᱟᱨᱵᱟᱝ ᱥᱟᱱᱟᱢ ᱰᱟᱩᱱᱞᱚᱰ ᱟᱠᱟᱱ ᱯᱷᱤᱞ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ?</string>
+    <string name="max_retry_desc">ᱰᱟᱩᱱᱞᱚᱰ ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚ ᱞᱟᱹᱜᱤᱫ ᱡᱚᱛᱚ ᱠᱷᱚᱱ ᱵᱟᱲᱛᱤ ᱥᱟᱸᱣᱛᱟ</string>
+    <string name="pause_downloads_on_mobile_desc">ᱢᱚᱵᱟᱤᱞ ᱰᱟᱴᱟ ᱨᱮ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱵᱮᱵᱚᱦᱟᱨᱚᱜ ᱟ, ᱢᱮᱱᱠᱷᱟᱱ ᱛᱤᱱᱟᱹᱜ ᱜᱟᱱ ᱰᱟᱩᱱᱞᱚᱰ ᱠᱚ ᱵᱚᱫᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ ᱟ</string>
+    <string name="enable_queue_limit_desc">ᱢᱤᱫᱴᱟᱹᱝ ᱰᱟᱩᱱᱞᱚᱰ ᱢᱤᱫ ᱚᱠᱛᱚ ᱨᱮ ᱪᱟᱞᱟᱜᱼᱟ</string>
+    <string name="pause_downloads">ᱰᱟᱩᱱᱞᱚᱰ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="downloads_storage_ask_summary">ᱡᱚᱛᱚ ᱰᱟᱩᱱᱞᱚᱰ ᱠᱚ ᱡᱟᱦᱟᱸ ᱨᱮ ᱫᱚᱦᱚᱭ ᱢᱮ ᱚᱱᱟ ᱟᱢᱮᱢ ᱩᱫᱩᱜᱟ._x000D_
+\nᱡᱩᱫᱤ ᱟᱢ ᱵᱟᱦᱨᱮ SD ᱠᱟᱨᱰ ᱨᱮ ᱰᱟᱩᱱᱞᱚᱰ ᱞᱟᱹᱜᱤᱫ ᱮᱠᱴᱤᱵᱷ ᱢᱮ</string>
+    <string name="downloads_storage_ask_summary_no_saf_notice">ᱟᱢ ᱡᱟᱦᱟᱸ ᱨᱮ ᱡᱚᱛᱚ ᱰᱟᱩᱱᱞᱚᱰ ᱥᱟᱦᱴᱟ ᱠᱚ ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚᱭ ᱢᱮ</string>
+    <string name="downloads_storage_use_saf_summary">\'ᱥᱴᱚᱨᱮᱡᱽ ᱮᱠᱥᱮᱥ ᱯᱷᱨᱮᱢᱠᱣᱟᱨᱠ\' ᱢᱤᱫ ᱵᱟᱦᱨᱮ SD ᱠᱟᱨᱰ ᱨᱮ ᱰᱟᱩᱱᱞᱚᱰ ᱞᱟᱹᱜᱤᱫ ᱮᱢᱚᱜ ᱟ</string>
+    <string name="downloads_storage_use_saf_summary_api_29">ᱚᱸᱰᱨᱚᱭᱮᱰ ᱑᱐ ᱠᱷᱚᱱ ᱮᱛᱚᱦᱚᱵ ᱠᱟᱛᱮ \'ᱥᱴᱳᱨᱮᱡ ᱮᱠᱥᱮᱥ ᱯᱷᱨᱮᱢᱚᱠ\' ᱜᱮ ᱥᱚᱯᱷᱚᱨᱴ ᱟᱠᱟᱱᱟ</string>
+    <string name="choose_instance_prompt">ᱢᱤᱫ ᱤᱱᱥᱴᱟᱱᱥ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="app_language_title">ᱟᱯᱞᱤᱠᱮᱥᱚᱱ ᱯᱟᱹᱨᱥᱤ</string>
+    <string name="systems_language">ᱥᱤᱥᱴᱮᱢ ᱰᱤᱯᱚᱞᱴ</string>
+    <string name="remove_duplicates_title">ᱰᱩᱯᱞᱤᱠᱮᱴ ᱠᱚ ᱪᱷᱩᱴᱟᱹᱣ?</string>
+    <string name="remove_duplicates_message">ᱟᱢ ᱱᱚᱶᱟ ᱯᱷᱟᱭᱞᱤᱥᱴ ᱨᱮ ᱡᱚᱛᱚ ᱫᱩᱯᱞᱤᱠᱮᱴ ᱥᱴᱨᱤᱢ ᱠᱚ ᱦᱮᱡ ᱟᱫᱮᱭᱟᱢ ᱥᱮ?</string>
+    <string name="remove_watched_popup_warning">ᱥᱮᱨᱮᱧ ᱛᱟᱹᱞᱤᱠᱟ ᱨᱮ ᱥᱮᱞᱮᱫᱚᱜ ᱢᱟᱲᱟᱝ ᱟᱨ ᱛᱟᱭᱚᱢ ᱨᱮ ᱡᱟᱦᱟᱸ ᱵᱷᱤᱰᱤᱣ ᱠᱚ ᱧᱮᱞ ᱟᱠᱟᱱᱟ, ᱚᱱᱟ ᱠᱚ ᱪᱷᱟᱹᱲ ᱮᱢ ᱦᱩᱭᱩᱜᱼᱟ ᱾ ᱟᱢ ᱠᱤ ᱜᱚᱴᱟᱵᱩᱴᱟᱹ ᱠᱟᱱᱟ? ᱱᱚᱶᱟ ᱫᱚ ᱨᱩᱣᱟᱹᱲ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾</string>
+    <string name="feed_new_items">ᱱᱟᱣᱟ ᱡᱚᱢᱟᱜ ᱡᱤᱱᱤᱥ ᱠᱚ</string>
+    <string name="feed_group_dialog_select_subscriptions">ᱥᱮᱞᱮᱫ ᱠᱚ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="feed_group_dialog_empty_selection">ᱪᱮᱫ ᱦᱚᱸ ᱵᱟᱱᱟᱣ ᱟᱠᱟᱱᱟ</string>
+    <string name="feed_group_show_only_ungrouped_subscriptions">ᱡᱟᱦᱟᱱᱟᱜ ᱵᱟᱝ ᱜᱩᱴ ᱟᱠᱟᱱ ᱥᱚᱵᱥᱠᱨᱟᱭᱵᱮᱥ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
+    <string name="settings_category_feed_title">ᱯᱷᱮᱰ</string>
+    <string name="feed_update_threshold_title">ᱯᱷᱤᱰ ᱟᱯᱰᱮᱴ ᱞᱟᱦᱟᱨᱮ</string>
+    <string name="feed_update_threshold_summary">ᱢᱤᱫ ᱥᱚᱵᱥᱠᱨᱟᱭᱵᱽᱥᱚᱱ ᱵᱟᱥᱠᱮᱴᱮᱰ ᱦᱩᱭᱩᱜ ᱢᱟᱬᱟᱝ ᱨᱮ ᱯᱟᱹᱪᱷᱤᱢ ᱩᱫᱩᱜ ᱠᱷᱚᱱ ᱛᱟᱭᱚᱢ ᱚᱠᱛᱚ ⁇ %s</string>
+    <string name="feed_load_error">Error loading feed ᱥᱟᱯᱲᱟᱣ</string>
+    <string name="feed_load_error_account_info">\'%s\' ᱞᱟᱹᱜᱤᱫ ᱯᱷᱤᱰ ᱞᱚᱰ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ.</string>
+    <string name="feed_load_error_terminated">ᱮᱠᱴᱚᱨᱟᱜ ᱮᱠᱟᱶᱴ ᱫᱚ ᱛᱷᱟᱯᱚᱱ ᱟᱠᱟᱱᱟ ᱾
+\nNewPipe ᱫᱚ ᱱᱚᱣᱟ ᱯᱷᱤᱰ ᱫᱚ ᱟᱵᱚ ᱦᱟᱵᱤᱡ ᱵᱟᱝ ᱞᱚᱰ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾
+\nᱟᱢ ᱱᱚᱣᱟ ᱪᱟᱱᱮᱨ ᱠᱷᱚᱱ ᱟᱹᱪᱩᱨ ᱚᱪᱚᱜ ᱠᱟᱹᱢᱤᱭᱟᱢ ᱥᱮ?</string>
+    <string name="feed_load_error_fast_unknown">ᱯᱷᱚᱥᱴ ᱯᱷᱤᱰ ᱢᱚᱰ ᱱᱚᱶᱟ ᱨᱮ ᱵᱟᱹᱲᱛᱤ ᱵᱟᱰᱟᱭ ᱮᱢ ᱮᱫᱟ ᱾</string>
+    <string name="feed_use_dedicated_fetch_method_title">ᱡᱩᱫᱤ ᱢᱮᱱᱟᱜᱼᱟ ᱮᱢ ᱟᱠᱟᱱ ᱡᱚᱢᱟᱜ ᱠᱷᱚᱱ ᱦᱟᱛᱟᱣ</string>
+    <string name="feed_use_dedicated_fetch_method_summary">ᱟᱭᱢᱟ ᱥᱮᱵᱟ ᱨᱮ ᱧᱟᱢᱚᱜᱼᱟ, ᱱᱚᱶᱟ ᱫᱚ ᱟᱥᱚᱠᱟᱭ ᱛᱮ ᱟᱹᱰᱤ ᱦᱩᱰᱤᱧ ᱜᱮᱭᱟ ᱢᱮᱱᱠᱷᱟᱱ ᱢᱤᱫ ᱞᱮᱠᱟᱱ ᱤᱱᱴᱮᱢᱥ ᱟᱨ ᱟᱥᱚᱠᱟᱭ ᱛᱮ ᱵᱟᱝ ᱯᱩᱨᱟᱹᱣ ᱟᱠᱟᱱ ᱤᱱᱯᱷᱚᱨᱢᱮᱥᱚᱱ (ᱡᱮᱞᱮᱠᱟ ᱺ ᱚᱠᱛᱚ ᱵᱟᱝ, ᱤᱱᱴᱮᱢᱥ ᱨᱮᱱᱟᱜ ᱞᱮᱠᱷᱟ, ᱚᱞᱤᱵᱽ ᱥᱴᱮᱴᱥ ᱵᱟᱝ) ᱨᱩᱣᱟᱹᱲ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ</string>
+    <string name="feed_use_dedicated_fetch_method_disable_button">ᱯᱷᱮᱥᱵᱩᱠ ᱢᱚᱰ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="feed_use_dedicated_fetch_method_help_text">ᱟᱢᱟᱜ ᱢᱚᱱᱮ ᱨᱮ ᱡᱚᱢᱟᱜ ᱨᱮᱱᱟᱜ ᱞᱚᱰ ᱟᱹᱰᱤ ᱟᱞᱜᱟ ᱜᱮᱭᱟ? ᱡᱩᱫᱤ ᱱᱚᱶᱟ ᱦᱩᱭᱩᱜ ᱠᱟᱱᱟ, ᱮᱱᱠᱷᱟᱱ ᱥᱯᱤᱰ ᱞᱚᱰ ᱮᱱᱟᱝ ᱮᱱᱮᱡ (ᱡᱟᱦᱟᱸ ᱫᱚ ᱟᱢ ᱥᱤᱴᱤᱝ ᱨᱮ ᱥᱮ ᱞᱟᱛᱟᱨ ᱨᱮᱭᱟᱜ ᱵᱩᱛᱟᱹᱢ ᱚᱞ ᱠᱟᱛᱮᱢ ᱵᱚᱫᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ)
+\n
+\nᱱᱤᱣ ᱯᱟᱭᱤᱯ ᱫᱚ ᱵᱟᱨᱭᱟ ᱯᱷᱤᱰ ᱞᱚᱰ ᱥᱴᱨᱮᱴᱡᱤ ᱮᱢᱚᱜ ᱠᱟᱱᱟ:
+\n• ᱡᱚᱛᱚ ᱥᱚᱵᱽᱥᱠᱨᱟᱭᱵᱽ ᱪᱮᱱᱮᱞ ᱦᱟᱛᱟᱣ, ᱡᱟᱦᱟᱸ ᱫᱚ ᱟᱞᱜᱟ ᱜᱮᱭᱟ ᱢᱮᱱᱠᱷᱟᱱ ᱯᱩᱨᱟᱹᱣᱜᱼᱟ ᱾
+\n• ᱰᱮᱰᱤᱠᱮᱴᱮᱰ ᱥᱮᱵᱟ ᱮᱱᱰ ᱯᱚᱭᱮᱱᱴ ᱵᱮᱵᱷᱟᱨ, ᱡᱟᱦᱟᱸ ᱫᱚ ᱦᱩᱲᱟᱹᱜ ᱜᱮᱭᱟ ᱢᱮᱱᱠᱷᱟᱱ ᱟᱥᱚᱠᱟᱭ ᱛᱮ ᱯᱩᱨᱟᱹᱣ ᱫᱚ ᱵᱟᱝ ᱦᱩᱭᱩᱜᱼᱟ ᱾
+\n
+\nᱵᱟᱨᱭᱟ ᱛᱟᱞᱟᱨᱮᱱᱟᱜ ᱵᱷᱮᱜᱟᱨ ᱫᱚ ᱦᱩᱭᱩᱜ ᱠᱟᱱᱟ ᱡᱮ ᱦᱩᱲᱟᱹᱜ ᱢᱤᱫᱴᱟᱹᱝ ᱜᱮ ᱤᱱᱯᱷᱚᱨᱢᱮᱥᱚᱱ ᱵᱟᱹᱱᱩᱜᱼᱟ, ᱡᱮᱞᱮᱠᱟ ᱤᱢᱮᱠᱥ ᱨᱮᱱᱟᱜ ᱚᱠᱛᱚ ᱥᱮ ᱛᱷᱚᱠ (ᱡᱟᱦᱟᱸ ᱫᱚ ᱞᱟᱭᱤᱵᱷᱤᱰᱤᱭᱳ ᱟᱨ ᱱᱚᱨᱢᱟᱞ ᱨᱮᱱᱟᱜ ᱵᱷᱮᱜᱟᱨ ᱵᱟᱭ ᱵᱟᱰᱟᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ) ᱟᱨ ᱱᱚᱶᱟ ᱠᱚᱢ ᱤᱢᱮᱠᱥ ᱠᱚ ᱨᱩᱣᱟᱹᱲ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾
+\n
+\nᱭᱩᱴᱭᱩᱵᱽ ᱫᱚ ᱢᱤᱫᱴᱮᱱ ᱥᱮᱵᱟ ᱨᱮᱱᱟᱜ ᱢᱤᱫ ᱞᱟᱹᱱᱟᱹᱤ ᱠᱟᱱᱟ ᱡᱟᱦᱟᱸ ᱫᱚ ᱱᱚᱶᱟ ᱦᱚᱨᱟ ᱟᱡᱟᱜ RSS ᱯᱷᱤᱰ ᱥᱟᱶᱛᱮ ᱮᱢᱚᱜ ᱠᱟᱱᱟ ᱾
+\n
+\nᱚᱱᱟᱛᱮ ᱵᱟᱪᱷᱟᱣ ᱫᱚ ᱟᱢ ᱪᱮᱫ ᱜᱮᱢ ᱵᱷᱟᱹᱵᱤᱛᱟ: ᱦᱩᱲᱟᱹᱜ ᱟᱨᱵᱟᱝ ᱥᱟᱹᱠᱷᱭᱟᱹᱛ ᱠᱟᱛᱷᱟ ᱾</string>
+    <string name="feed_show_hide_streams">ᱥᱴᱨᱤᱢ ᱠᱚ ᱵᱚᱫᱚᱞ/ᱥᱚᱫᱚᱨ ᱢᱮ</string>
+    <string name="feed_fetch_channel_tabs_summary">ᱡᱚᱠᱷᱚᱱ ᱯᱷᱤᱰ ᱚᱯᱰᱮᱴ ᱦᱩᱭᱩᱜᱼᱟ, ᱚᱱᱟ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱴᱮᱵᱽ ᱠᱚ ᱦᱟᱛᱟᱣ ᱢᱮ ᱾ ᱡᱩᱫᱤ ᱢᱤᱫ ᱪᱮᱱᱮᱞ ᱫᱚ fast mode ᱵᱮᱵᱷᱟᱨ ᱠᱟᱛᱮ update ᱟᱠᱟᱱᱟ ᱮᱱᱠᱷᱟᱱ ᱱᱚᱶᱟ option ᱫᱚ ᱵᱟᱹᱱᱩᱜ-ᱟ ᱾</string>
+    <string name="content_not_supported">ᱱᱩᱣᱟ ᱯᱟᱭᱤᱯ ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡ ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱥᱚᱯᱷᱴᱣᱮᱭᱟᱨ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾_x000D_
+\n_x000D_
+\nᱱᱚᱶᱟ ᱫᱚ ᱢᱚᱵᱟᱤᱞ ᱣᱟᱨᱥᱚᱱ ᱨᱮ ᱥᱚᱯᱷᱴᱣᱮᱨ ᱦᱩᱭᱩᱜᱼᱟ ᱾</string>
+    <string name="show_thumbnail_summary">ᱞᱚᱠ ᱥᱠᱨᱤᱱ ᱛᱷᱚᱠ ᱟᱨ ᱱᱳᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱵᱟᱱᱟᱨ ᱞᱟᱹᱜᱤᱫ ᱛᱚᱢᱱᱤᱞ ᱵᱮᱵᱷᱟᱨ ᱢᱮ</string>
+    <string name="no_app_to_open_intent">ᱟᱢᱟᱜ ᱰᱤᱵᱟᱤᱥ ᱨᱮ ᱱᱚᱶᱟ ᱮᱯ ᱵᱟᱹᱱᱩᱜ-ᱟ</string>
+    <string name="no_appropriate_file_manager_message">ᱱᱚᱶᱟ ᱠᱟᱹᱢᱤ ᱞᱟᱹᱜᱤᱫ ᱫᱚ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱯᱷᱤᱞ ᱢᱮᱱᱮᱡᱚᱨ ᱵᱟᱝ ᱧᱟᱢ ᱞᱮᱱᱟ ᱾ %s</string>
+    <string name="georestricted_content">ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱟᱢᱟᱜ ᱫᱤᱥᱚᱢ ᱨᱮ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾</string>
+    <string name="soundcloud_go_plus_content">ᱱᱩᱶᱟ ᱫᱚ ᱢᱤᱫ ᱥᱟᱣᱚᱱᱰᱠᱞᱟᱣᱩᱰ ᱜᱳ+ ᱴᱨᱟᱠ ᱠᱟᱱᱟ, ᱟᱢᱟᱜ ᱫᱤᱥᱚᱢ ᱨᱮ ᱡᱚᱛᱚ ᱠᱷᱚᱱ ᱠᱚᱢ, ᱚᱱᱟᱛᱮ ᱱᱩᱭᱯᱟᱭᱤᱯ ᱫᱚ ᱱᱚᱣᱟ ᱴᱨᱟᱠ ᱥᱴᱨᱤᱢ ᱟᱨ ᱰᱟᱩᱱᱞᱚᱰ ᱵᱟᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱾</string>
+    <string name="private_content">ᱱᱩᱣᱟ ᱯᱟᱭᱤᱯ ᱫᱚ ᱱᱤᱡᱮᱨᱟᱜ ᱠᱟᱱᱟ, ᱚᱱᱟᱛᱮ ᱱᱩᱣᱟ ᱯᱟᱭᱤᱯ ᱫᱚ ᱱᱚᱣᱟ ᱵᱟᱭ ᱰᱟᱩᱱᱞᱚᱰ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱟᱨ ᱵᱟᱝ ᱥᱴᱨᱤᱢ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ᱾</string>
+    <string name="paid_content">ᱱᱩᱣᱟ ᱯᱟᱭᱤᱯ ᱨᱮᱱ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱠᱚ ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱡᱟᱦᱟᱸᱱ ᱯᱮᱨᱮᱡ ᱥᱮᱞᱮᱫ ᱠᱚᱣᱟ, ᱚᱱᱟᱛᱮ ᱱᱩᱣᱟ ᱯᱟᱭᱤᱯ ᱨᱮᱱ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱠᱚ ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱡᱟᱦᱟᱸᱱ ᱯᱮᱨᱮᱡ ᱥᱮᱞᱮᱫ ᱵᱟᱝ ᱠᱚ ᱧᱟᱢ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ᱾</string>
+    <string name="featured">ᱯᱷᱮᱭᱟᱨ ᱟᱠᱟᱱ</string>
+    <string name="night_theme_available">ᱱᱚᱶᱟ ᱵᱟᱪᱷᱟᱣ ᱫᱚ ᱚᱱᱠᱟᱱ ᱞᱟᱹᱜᱤᱫ ᱜᱮ ᱵᱮᱵᱦᱟᱨᱚᱜᱼᱟ ᱡᱩᱫᱤ %s ᱴᱷᱤᱠᱟᱹᱱᱟ</string>
+    <string name="download_has_started">ᱰᱟᱩᱱᱞᱚᱰ ᱮᱛᱚᱦᱚᱵ ᱟᱠᱟᱱᱟ</string>
+    <string name="description_select_note">ᱟᱢ ᱱᱤᱛᱚᱜ ᱚᱞ ᱪᱤᱠᱤ ᱨᱮ ᱚᱞ ᱪᱤᱠᱤ ᱵᱟᱪᱷᱟᱣ ᱫᱟᱲᱮᱭᱟᱜ ᱟ. ᱵᱟᱰᱟᱭ ᱢᱮ ᱡᱮ ᱥᱟᱦᱴᱟ ᱫᱚ ᱵᱷᱤᱸᱰᱟᱹᱣ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱟᱨ ᱵᱟᱪᱷᱟᱣ ᱢᱚᱰ ᱨᱮ ᱞᱤᱱᱠ ᱫᱚ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱾</string>
+    <string name="description_select_enable">ᱪᱤᱠᱤ ᱨᱮ ᱚᱞ ᱵᱟᱪᱷᱟᱣ ᱞᱟᱹᱜᱤᱫ ᱮᱱᱮᱢ ᱢᱮ</string>
+    <string name="metadata_privacy">ᱯᱨᱟᱭᱵᱷᱮᱴ</string>
+    <string name="metadata_age_limit">ᱩᱢᱚᱨ ᱨᱮᱭᱟᱜ ᱞᱤᱢᱤᱴ</string>
+    <string name="metadata_language">ᱯᱟᱹᱨᱥᱤ</string>
+    <string name="metadata_support">ᱥᱚᱦᱚᱫ</string>
+    <string name="metadata_thumbnails">ᱛᱷᱩᱢᱱᱟᱭᱤᱴ</string>
+    <string name="metadata_uploader_avatars">ᱚᱯᱞᱚᱰᱟᱨ ᱟᱵᱟᱛᱟᱨ</string>
+    <string name="metadata_subchannel_avatars">ᱥᱟᱵ-ᱪᱟᱱᱮᱞ ᱟᱵᱟᱛᱟᱨ</string>
+    <string name="metadata_privacy_private">ᱯᱨᱟᱭᱵᱷᱮᱴ</string>
+    <string name="metadata_privacy_internal">ᱤᱱᱴᱟᱨᱱᱟᱞ</string>
+    <string name="metadata_subscribers">ᱥᱚᱵᱽᱥᱠᱨᱟᱭᱵᱟᱨ</string>
+    <string name="detail_pinned_comment_view_description">ᱯᱤᱱ ᱟᱠᱟᱱ ᱟᱹᱱᱤᱡ</string>
+    <string name="detail_heart_img_view_description">ᱵᱮᱱᱟᱣᱤᱡᱟᱜ ᱢᱚᱱᱮ</string>
+    <string name="open_website_license">ᱚᱯᱮᱱ ᱣᱮᱵᱥᱟᱤᱴ</string>
+    <string name="tablet_mode_title">ᱴᱮᱵᱽᱞᱮᱴ ᱢᱚᱰ</string>
+    <string name="on">ᱨᱮ</string>
+    <string name="off">ᱚᱯᱷ</string>
+    <string name="progressive_load_interval_exoplayer_default">ᱮᱠᱥᱳᱯᱞᱮᱭᱟᱨ ᱰᱤᱯᱚᱞᱴ</string>
+    <string name="toggle_all">ᱥᱟᱱᱟᱢ ᱪᱟᱞᱟᱣ ᱢᱮ</string>
+    <string name="streams_not_yet_supported_removed">ᱡᱟᱦᱟᱸᱱ ᱥᱴᱨᱤᱢ ᱠᱚ ᱱᱤᱛ ᱫᱷᱟᱹᱵᱤᱡ ᱰᱟᱩᱱᱞᱚᱰᱡᱚᱨ ᱦᱚᱛᱮᱛᱮ ᱥᱚᱯᱷᱴᱣᱮᱰ ᱵᱟᱱᱟᱣ ᱟᱠᱟᱱᱟ ᱚᱱᱟ ᱠᱚ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="audio_track_present_in_video">ᱱᱚᱶᱟ ᱥᱴᱨᱤᱢ ᱨᱮ ᱢᱤᱫᱴᱟᱹᱝ ᱚᱰᱤᱭᱳ ᱴᱨᱟᱠ ᱢᱮᱱᱟᱜ ᱛᱟᱭᱟ</string>
+    <string name="feed_show_partially_watched">ᱡᱟᱹᱥᱛᱤ ᱧᱮᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="feed_show_upcoming">ᱢᱟᱲᱟᱝ ᱨᱮ</string>
+    <string name="use_exoplayer_decoder_fallback_summary">ᱡᱩᱫᱤ ᱟᱢᱟᱜ ᱰᱤᱠᱳᱰᱟᱨ ᱮᱛᱚᱦᱚᱵ ᱨᱮᱭᱟᱜ ᱞᱟᱹᱞᱤᱥ ᱛᱟᱦᱮᱸᱱᱟ, ᱮᱱᱠᱷᱟᱱ ᱱᱚᱶᱟ ᱵᱟᱪᱷᱱᱟᱣ ᱫᱚ ᱮᱥᱮᱨ ᱢᱮ, ᱡᱟᱦᱟᱫᱚ ᱮᱛᱚᱦᱚᱵ ᱰᱤᱠᱳᱰᱟᱨ ᱮᱛᱚᱦᱚᱵ ᱵᱟᱝ ᱦᱩᱭ ᱞᱮᱱ ᱠᱷᱟᱱ ᱞᱟᱛᱟᱨᱼᱯᱨᱳᱭᱨᱤᱴᱤ ᱰᱤᱠᱳᱰᱟᱨ ᱨᱮ ᱨᱩᱣᱟᱹᱲ ᱦᱮᱡᱚᱜᱼᱟ ᱾ ᱱᱚᱶᱟ ᱨᱮᱭᱟᱜ ᱚᱡᱮ ᱫᱚ ᱮᱛᱚᱦᱚᱵ ᱰᱤᱠᱳᱰᱟᱨ ᱠᱚ ᱵᱮᱣᱦᱟᱨ ᱚᱠᱛᱚ ᱠᱷᱚᱱ ᱵᱮᱼᱲᱤᱪᱟᱹᱲ ᱯᱟᱥᱱᱟᱶ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾</string>
+    <string name="always_use_exoplayer_set_output_surface_workaround_summary">ᱱᱚᱶᱟ ᱠᱟᱹᱢᱤ ᱦᱚᱨᱟ ᱫᱚ ᱥᱩᱨᱥᱩᱯᱩᱨ ᱵᱚᱫᱚᱞ ᱦᱩᱭ ᱞᱮᱱ ᱠᱷᱟᱱ ᱵᱷᱤᱰᱤᱭᱳ ᱠᱳᱰᱮᱠ ᱠᱚ ᱵᱚᱫᱚᱞ ᱟᱨ ᱨᱤ-ᱤᱱᱥᱴᱟᱱᱥᱟᱭᱤᱴ (re-instantiates) ᱮᱫᱟ, ᱚᱱᱟ ᱫᱚ ᱥᱩᱨᱥᱩᱯᱩᱨ ᱠᱳᱰᱮᱠ ᱨᱮ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱥᱩᱯᱩᱨ ᱥᱩᱯᱩᱨ ᱛᱮ ᱥᱮᱴᱮᱨᱚᱜ ᱨᱮᱱᱟᱜ ᱠᱟᱱᱟ ᱾ ᱱᱚᱶᱟ ᱥᱟᱛᱟᱢ ᱥᱟᱶᱛᱮ ᱮᱠᱥᱳᱯᱞᱮᱭᱟᱨ ᱟᱭᱢᱟ ᱚᱱᱚᱞ ᱨᱮ ᱵᱮᱵᱷᱟᱨ ᱟᱠᱟᱱᱟ, ᱱᱚᱶᱟ ᱥᱤᱴᱤᱝ ᱫᱚ ᱟᱱᱰᱨᱚᱭᱮᱰ ᱖ ᱟᱨ ᱚᱱᱟ ᱠᱷᱚᱱ ᱪᱮᱛᱟᱱ ᱨᱮᱜᱮ ᱟᱯᱷᱮᱠᱴ ᱟᱠᱟᱱᱟ
+\n
+\nᱱᱮᱛᱟᱨᱟᱜ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱟᱨ ᱥᱮ ᱯᱷᱩᱞᱥᱠᱨᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫᱚᱜ ᱡᱚᱠᱷᱚᱱ ᱱᱚᱶᱟ ᱵᱟᱪᱷᱟᱣ ᱮᱢ ᱫᱚᱦᱚ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ</string>
+    <string name="show_channel_tabs_summary">ᱪᱮᱱᱟᱞ ᱥᱟᱦᱴᱟ ᱠᱚᱨᱮ ᱪᱮᱫ ᱴᱮᱵᱽ ᱠᱚ ᱵᱚᱫᱚᱞᱟ</string>
+    <string name="open_play_queue">ᱚᱯᱮᱱ ᱯᱞᱮ ᱠᱭᱩᱤ</string>
+    <string name="toggle_fullscreen">ᱡᱚᱛᱚ ᱥᱠᱨᱤᱱ ᱨᱮ ᱵᱷᱮᱡᱟ ᱢᱮ</string>
+    <string name="next_stream">ᱱᱮᱛᱟᱨᱟᱜ ᱫᱟᱜᱽ</string>
+    <string name="play">ᱠᱷᱮᱞ</string>
+    <string name="replay">ᱨᱤᱯᱷᱟᱭᱤᱞ</string>
+    <string name="duration">ᱚᱠᱛᱚ</string>
+    <string name="rewind">ᱨᱩᱣᱟ.</string>
+    <string name="image_quality_summary">ᱰᱮᱴᱟ ᱟᱨ ᱢᱮᱢᱚᱨᱤ ᱵᱮᱵᱷᱟᱨ ᱠᱚᱢ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱪᱤᱛᱟᱹᱨ ᱠᱚᱣᱟᱜ ᱥᱤᱠᱷᱱᱟᱹᱛ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ᱟᱨ ᱪᱤᱛᱟᱹᱨ ᱠᱚ ᱚᱞ ᱦᱚᱪᱚ ᱞᱟᱹᱜᱤᱫ ᱛᱮ ᱵᱟᱪᱷᱟᱣ ᱢᱮ. ᱢᱤᱢᱮᱨᱤ ᱟᱨ ᱚᱱ-ᱰᱤᱥᱠ ᱪᱤᱛᱟᱹᱨ ᱠᱮᱪ ᱵᱟᱱᱟᱨ ⁇ %s ᱪᱷᱟᱯᱟ ᱟᱠᱟᱱᱟ</string>
+    <string name="image_quality_low">ᱞᱟᱯᱷᱟᱝ ᱥᱤᱠᱷᱱᱟ.</string>
+    <string name="image_quality_medium">ᱛᱟᱞᱢᱟ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
+    <string name="image_quality_high">ᱩᱥᱩᱞ ᱥᱤᱠᱷᱱᱟᱹᱛ</string>
+    <string name="share_playlist_with_titles_message">ᱯᱷᱟᱭᱞᱤᱥᱴ ᱧᱩᱛᱩᱢ ᱟᱨ ᱵᱷᱤᱰᱤᱭᱳ ᱧᱩᱛᱩᱢ ᱞᱮᱠᱟᱛᱮ ᱟᱨᱵᱟᱝ ᱵᱷᱤᱰᱤᱭᱳ URL ᱨᱮᱱᱟᱜ ᱢᱤᱫ ᱞᱮᱠᱟᱱ ᱞᱤᱥᱴᱤ ᱞᱮᱠᱟᱛᱮ ᱴᱷᱟᱶ ᱮᱢ ᱢᱮ</string>
+    <string name="share_playlist_with_list">URL ᱛᱟᱹᱞᱠᱟᱹ ᱥᱟᱯᱲᱟᱣ</string>
+    <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="notification_scale_to_square_image_summary">ᱱᱚᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ ᱨᱮ ᱑᱖:᱙ ᱠᱷᱚᱱ ᱑:᱑ ᱟᱥᱯᱮᱠᱴ ᱚᱱᱩᱯᱟᱹᱛ ᱨᱮ ᱵᱷᱤᱰᱤᱭᱳ ᱛᱷᱚᱢᱵᱱᱮᱞ ᱜᱮᱫᱽ ᱢᱮ</string>
+    <string name="recovering">ᱨᱤᱠᱟᱵᱽᱞᱤᱝ</string>
+    <string name="default_content_country_title">ᱰᱤᱯᱷᱚᱞᱴ ᱠᱚᱱᱴᱮᱱᱴ ᱫᱤᱥᱚᱢ</string>
+    <string name="playback_pitch">ᱯᱤᱪ</string>
+    <string name="metadata_host">ᱦᱳᱥᱴ</string>
+    <string name="use_exoplayer_decoder_fallback_title">ExoPlayer ᱨᱮᱭᱟᱜ ᱰᱤᱠᱚᱰᱟᱨ ᱯᱷᱮᱞᱚᱵᱮᱠ ᱯᱷᱤᱪᱚᱨ ᱵᱮᱵᱷᱟᱨ ᱢᱮ</string>
+    <string name="settings_category_exoplayer_title">ᱮᱠᱥᱳᱯᱞᱮᱭᱟᱨ ᱥᱮᱴᱤᱝ</string>
+    <string name="settings_category_exoplayer_summary">ᱮᱠᱥᱳᱯᱞᱮᱭᱟᱨ ᱥᱮᱴᱤᱸᱜᱽᱥ ᱠᱚ ᱪᱟᱪᱞᱟᱣ ᱢᱮ ᱾ ᱱᱚᱶᱟ ᱞᱟᱹᱜᱤᱫ ᱠᱷᱮᱞᱚᱸᱰᱤᱭᱟᱹ ᱠᱚᱣᱟᱜ ᱨᱤᱥᱴᱟᱨᱴ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ</string>
+    <string name="audio_track_type_dubbed">ᱰᱟᱵᱞᱤᱝ</string>
+    <string name="audio_track_type_descriptive">ᱰᱮᱥᱠᱨᱤᱯᱴᱤᱵ</string>
+    <string name="progressive_load_interval_summary">ᱯᱨᱚᱜᱨᱮᱥᱤᱵᱽ ᱠᱚᱴᱮᱱᱴ (ᱱᱤᱛᱚᱜ %s) ᱨᱮ ᱞᱚᱰ ᱤᱱᱴᱟᱨᱵᱷᱮᱞ ᱨᱮᱱᱟᱜ ᱢᱟᱨᱟᱝ ᱮᱢ ᱢᱮ ᱢᱤᱫ ᱠᱚᱢ ᱵᱷᱚᱞᱮᱡᱽ ᱟᱡᱟᱜ ᱮᱛᱚᱦᱚᱵ ᱞᱚᱰᱰᱤᱝ ᱨᱮᱱᱟᱜ ᱟᱯᱞᱚᱰ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ</string>
+    <string name="prefer_original_audio_title">ᱚᱥᱴᱨᱮᱞᱤᱭᱟᱱ ᱚᱰᱤᱭᱳ ᱞᱟᱹᱜᱤᱫ</string>
+    <string name="local_search_suggestions">ᱴᱚᱴᱷᱟᱠᱤᱭᱟ. ᱥᱩᱪᱚᱱᱟ</string>
+    <string name="enable_playback_resume_title">ᱠᱷᱚᱵᱚᱨ ᱮᱦᱚᱵ ᱢᱮ</string>
+    <string name="enable_playback_resume_summary">ᱢᱩᱪᱟᱹᱫ ᱠᱷᱚᱵᱚᱨ ᱴᱷᱟᱹᱶᱠᱟᱹ</string>
+    <string name="peertube_instance_add_title">ᱤᱱᱥᱴᱮᱸᱥ ᱥᱮᱞᱮᱫ ᱢᱮ</string>
+    <string name="peertube_instance_add_help">ᱤᱱᱥᱴᱟᱱᱥ URL ᱮᱢ ᱢᱮ</string>
+    <string name="settings_category_updates_title">ᱚᱯᱰᱮᱴᱥ</string>
+    <string name="popup_playing_toast">ᱯᱚᱯ-ᱟᱯ ᱢᱚᱰ ᱨᱮ ᱠᱷᱮᱞᱚᱸᱰ</string>
+    <string name="content">ᱥᱟᱦᱴᱟ</string>
+    <string name="show_age_restricted_content_title">ᱩᱢᱮᱨ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱠᱟᱛᱷᱟ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="youtube_restricted_mode_enabled_title">ᱭᱩᱴᱭᱩᱵᱽ ᱨᱮᱱᱟᱜ \"Restricted Mode\" ᱚᱞ ᱢᱮ</string>
+    <string name="all">ᱡᱚᱛᱚ</string>
+    <string name="network_error">ᱱᱮᱴᱣᱟᱨᱠ ᱵᱷᱩᱞ</string>
+    <string name="app_description">ᱟᱱᱰᱨᱚᱭᱮᱰ ᱨᱮ ᱞᱤᱵᱨᱮ ᱞᱟᱭᱤᱴᱣᱮᱴ ᱥᱴᱨᱤᱢᱤᱝ</string>
+    <string name="metadata_tags">ᱴᱮᱜᱥ</string>
+    <string name="metadata_privacy_public">ᱥᱚᱨᱠᱟᱨᱤ</string>
+    <string name="error_insufficient_storage">ᱚᱱᱚᱞ ᱨᱮ ᱡᱟᱭᱜᱟ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="detail_sub_channel_thumbnail_view_description">ᱪᱮᱱᱮᱞ ᱨᱮᱱᱟᱜ ᱟᱵᱟᱛᱟᱨ ᱛᱷᱩᱱᱤᱠᱟ</string>
+    <string name="playlist_page_summary">ᱥᱮᱨᱮᱧ ᱞᱤᱥᱴᱤ ᱥᱟᱦᱴᱟ</string>
+    <string name="never">ᱵᱟᱝ</string>
+    <string name="settings_category_downloads_title">ᱰᱟᱩᱱᱞᱚᱰ ᱢᱮ</string>
+    <string name="settings_file_charset_title">ᱯᱷᱤᱞ ᱧᱩᱛᱩᱢ ᱨᱮ ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱪᱤᱠᱤ ᱠᱚ</string>
+    <string name="msg_failed_to_copy">ᱪᱷᱟᱸᱪ:Copy to clipboard ᱨᱮ ᱪᱷᱟᱸᱪ:Copy to clipboard</string>
+    <string name="rename">ᱧᱩᱛᱩᱢ ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="msg_name">ᱯᱷᱤᱞᱤᱢ ᱧᱩᱛᱩᱢ</string>
+    <string name="msg_threads">ᱛᱷᱤᱭᱮᱴᱚᱨ</string>
+    <string name="msg_error">ᱪᱷᱟᱸᱪ</string>
+    <string name="msg_running">NewPipe ᱮᱯᱞᱤᱠᱮᱥᱚᱱ</string>
+    <string name="msg_running_detail">ᱵᱟᱰᱟᱭ ᱞᱟᱹᱜᱤᱫ ᱴᱟᱯ ᱢᱮ</string>
+    <string name="msg_calculating_hash">ᱦᱟᱥᱤ ᱠᱚᱞᱠᱟᱛᱟ</string>
+    <string name="no_dir_yet">ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡ ᱰᱟᱩᱱᱞᱚᱰ ᱯᱷᱳᱞᱰᱟᱨ ᱥᱮᱴ ᱵᱟᱝ, ᱱᱤᱛ ᱦᱟᱹᱵᱤᱡ ᱰᱟᱩᱱᱞᱚᱰ ᱯᱷᱳᱞᱰᱟᱨ ᱵᱟᱪᱷᱟᱣ ᱢᱮ</string>
+    <string name="copyright">© %1$s ᱛᱮ %2$s ᱞᱟᱛᱟᱨ ᱨᱮ %3$s</string>
+    <string name="tab_about">About &amp; FAQ</string>
+    <string name="tab_licenses">ᱞᱟᱭᱥᱮᱱᱥ</string>
+    <string name="contribution_title">ᱥᱮᱞᱮᱫᱚᱜ ᱢᱮ</string>
+    <string name="privacy_policy_title">ᱱᱤᱭᱩ ᱯᱟᱭᱯᱮ ᱨᱮᱱᱟᱜ ᱯᱨᱟᱭᱵᱷᱮᱴᱤ ᱯᱚᱞᱤᱥᱤ</string>
+    <string name="privacy_policy_encouragement">ᱱᱤᱣ ᱯᱟᱭᱤᱯ ᱯᱨᱚᱡᱮᱠᱴ ᱟᱢᱟᱜ ᱯᱨᱟᱭᱵᱷᱮᱴ ᱟᱹᱰᱤ ᱥᱚᱨᱮᱥ ᱜᱮᱭᱟ. ᱚᱱᱟᱛᱮ, ᱟᱢᱟᱜ ᱟᱹᱭᱫᱟᱹᱨᱤ ᱵᱟᱹᱱᱩᱜ ᱠᱷᱟᱱ ᱱᱚᱶᱟ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱡᱟᱦᱟᱱ ᱰᱮᱴᱟ ᱵᱟᱭ ᱥᱟᱢᱟᱝᱟ ᱾_x000D_
+\nᱱᱤᱣ ᱯᱟᱭᱤᱯ ᱨᱮᱱᱟᱜ ᱯᱨᱟᱭᱵᱷᱮᱴ ᱯᱚᱞᱤᱥᱤ ᱨᱮ ᱡᱟᱦᱟᱸᱱ ᱰᱮᱴᱟ ᱠᱚ ᱟᱢ ᱠᱨᱮᱥᱚᱨ ᱨᱮᱯᱳᱨᱴ ᱚᱠᱟᱛᱮ ᱟᱢ ᱚᱠᱟ ᱞᱮᱠᱟᱛᱮ ᱟᱢᱟᱜ ᱰᱮᱴᱟ ᱠᱚ ᱵᱚᱫᱚᱞᱟ ᱟᱨ ᱵᱚᱫᱚᱞᱟ ᱚᱱᱟ ᱵᱟᱵᱚᱫ ᱛᱮ ᱡᱚᱛᱚ ᱵᱟᱰᱟᱭ ᱮᱢᱚᱜ ᱟ᱾</string>
+    <string name="read_privacy_policy">ᱯᱨᱟᱭᱵᱷᱮᱴᱤ ᱯᱚᱞᱤᱥ ᱯᱟᱲᱦᱟᱣ ᱢᱮ</string>
+    <string name="read_full_license">ᱞᱟᱭᱥᱮᱱᱥ ᱯᱟᱲᱦᱟᱣ ᱢᱮ</string>
+    <string name="faq_title">ᱟᱥᱚᱠᱟᱭ ᱛᱮ ᱚᱞ ᱟᱠᱟᱱ ᱯᱩᱥᱴᱟ</string>
+    <string name="missing_file">ᱯᱷᱤᱞ ᱥᱮᱴᱮᱨ ᱟᱠᱟᱱᱟ ᱟᱨ ᱵᱟᱝ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ</string>
+    <string name="invalid_directory">ᱱᱚᱝᱠᱟᱱ ᱯᱷᱳᱞᱰᱟᱨ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="invalid_source">ᱱᱚᱝᱠᱟᱱ ᱯᱷᱤᱞ/ᱥᱟᱹᱛ ᱨᱮᱱᱟᱜ ᱜᱟᱞᱢᱟᱨᱟᱣ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="loading_metadata_title">ᱢᱮᱴᱟᱰᱟᱴᱟ ᱞᱚᱰ ᱠᱟᱱᱟ…</string>
+    <string name="channels">ᱪᱟᱱᱮᱞ</string>
+    <string name="undo">ᱵᱚᱫᱚᱞ ᱢᱮ</string>
+    <string name="play_all">ᱥᱟᱱᱟᱢ ᱠᱷᱮᱞ ᱢᱮ</string>
+    <string name="always">ᱡᱚᱦᱚᱜ</string>
+    <string name="just_once">ᱢᱤᱫ ᱫᱷᱟᱣ</string>
+    <string name="file">ᱯᱷᱤᱞᱤ</string>
+    <string name="notifications">ᱱᱳᱴᱤᱯᱷᱤᱠᱮᱥᱚᱱ</string>
+    <string name="notification_channel_name">ᱱᱤᱭᱩ ᱯᱟᱭᱯᱮ ᱵᱟᱰᱟᱭ ᱦᱚᱪᱚ</string>
+    <string name="playback_step">ᱥᱴᱮᱯ</string>
+    <string name="error_connect_host">ᱥᱮᱵᱟ ᱨᱮ ᱡᱚᱲᱟᱣ ᱵᱟᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
+    <string name="error_http_not_found">ᱵᱟᱝ ᱧᱟᱢ ᱟᱠᱟᱱᱟ</string>
+    <string name="sort">ᱥᱮᱞᱮᱫ</string>
+    <string name="error_snackbar_message">ᱤᱧ ᱵᱟᱹᱧ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ, ᱡᱟᱦᱟᱸᱱᱟᱜ ᱠᱷᱟᱹᱛᱤᱨ ᱤᱧ ᱵᱟᱹᱧ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾</string>
+    <string name="streams_notifications_network_title">ᱞᱟᱹᱠᱛᱤᱭᱟᱱ ᱱᱮᱴᱣᱟᱨᱠ ᱡᱚᱲᱟᱣ</string>
+    <string name="any_network">ᱡᱟᱦᱟᱱ ᱱᱮᱴᱣᱟᱨᱠ</string>
+    <string name="close">ᱥᱩᱯᱩᱨ</string>
+    <string name="more_options">ᱰᱷᱮᱨ ᱵᱟᱪᱷᱟᱣ</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index cbc248b8b..85f248b11 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -23,4 +23,11 @@
     <string name="controls_add_to_playlist_title">ඇතුලත් කරන්න මෙයට</string>
     <string name="download_path_title">චලන රූප භාගත කරන ස්තානය</string>
     <string name="download_path_dialog_title">චලන රූප ගබඩා කිරීමට ස්තානයක් තෝරන්න</string>
+    <string name="open_in_popup_mode">උත්පතන මාදිලියේ විවෘත කරන්න</string>
+    <string name="share_dialog_title">සමඟ බෙදාගන්න</string>
+    <string name="use_external_audio_player_title">බාහිර ශ්‍රව්‍ය වාදකය භාවිතා කරන්න</string>
+    <string name="use_external_video_player_summary">සමහර විභේදනවලදී හඬ ඉවත් කරයි</string>
+    <string name="no_player_found_toast">ප්‍රවාහ වාදකයක් හමු නොවීය (ඔබට එය වාදනය කිරීමට VLC ස්ථාපනය කළ හැක).</string>
+    <string name="controls_download_desc">ප්‍රවාහ ගොනුව බාගන්න</string>
+    <string name="no_player_found">ප්‍රවාහ වාදකයක් හමු නොවීය. VLC ස්ථාපනය කරන්නද?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 3ad8a7023..a76bc1d4d 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -15,15 +15,15 @@
     <string name="use_external_audio_player_title">Použiť externý prehrávač zvuku</string>
     <string name="download_path_title">Prevzaté video ukladať do</string>
     <string name="download_path_summary">Prevzaté video súbory sú uložené tu</string>
-    <string name="download_path_dialog_title">Vyberte priečinok na ukladanie videí</string>
-    <string name="download_path_audio_title">Adresár pre stiahnuté audio</string>
-    <string name="download_path_audio_dialog_title">Vyberte adresár pre ukladanie audio súborov</string>
+    <string name="download_path_dialog_title">Vyberte priečinok pre stiahnuté video</string>
+    <string name="download_path_audio_title">Priečinok pre stiahnuté audio</string>
+    <string name="download_path_audio_dialog_title">Vyberte priečinok pre audio súbory</string>
     <string name="download_path_audio_summary">Prevzaté zvukové súbory sú uložené tu</string>
     <string name="default_resolution_title">Štandardné rozlíšenie</string>
     <string name="play_with_kodi_title">Prehrať cez Kodi</string>
     <string name="kore_not_found">Nainštalovať chýbajúcu aplikáciu Kore\?</string>
     <string name="show_play_with_kodi_title">Zobraziť možnosť \"Prehrať cez Kodi\"</string>
-    <string name="show_play_with_kodi_summary">Zobraziť možnosť \"Prehrať cez Kodi\"</string>
+    <string name="show_play_with_kodi_summary">Zobrazí možnosť prehrať video cez mediálne centrum Kodi</string>
     <string name="play_audio">Zvuk</string>
     <string name="default_audio_format_title">Predvolený zvukový formát</string>
     <string name="theme_title">Téma</string>
@@ -86,7 +86,7 @@
     <string name="short_million">M</string>
     <string name="short_billion">B</string>
     <string name="recaptcha_request_toast">Požiadavka reCAPTCHA</string>
-    <string name="open_in_popup_mode">Spustiť v okne</string>
+    <string name="open_in_popup_mode">Otvoriť vo vyskakovacom okne</string>
     <string name="msg_popup_permission">Tieto práva sú potrebné pre
 \nprehrávanie v mini okne</string>
     <string name="default_video_format_title">Preferovaný formát videa</string>
@@ -100,11 +100,11 @@
     <string name="clear">Vyčistiť</string>
     <string name="use_external_video_player_summary">Odstráni audio pri niektorých rozlíšeniach</string>
     <string name="popup_remember_size_pos_title">Zapamätať si parametre mini okna</string>
-    <string name="popup_remember_size_pos_summary">Zapamätať si posledné nastavenie veľkosti a pozície mini okna</string>
+    <string name="popup_remember_size_pos_summary">Zapamätá si posledné nastavenie veľkosti a pozície vyskakovacieho okna</string>
     <string name="show_search_suggestions_title">Hľadať návrhy</string>
     <string name="show_search_suggestions_summary">Vyberte návrhy, ktoré sa majú zobrazovať pri vyhľadávaní</string>
     <string name="best_resolution">Najlepšie rozlíšenie</string>
-    <string name="subscribe_button_title">Odber</string>
+    <string name="subscribe_button_title">Odoberať</string>
     <string name="subscribed_button_title">Odoberané</string>
     <string name="channel_unsubscribed">Odber zrušený</string>
     <string name="subscription_change_failed">Nemožno zmeniť odber</string>
@@ -112,16 +112,16 @@
     <string name="tab_subscriptions">Odbery</string>
     <string name="fragment_feed_title">Čo je nové</string>
     <string name="enable_search_history_title">Hľadať v histórií</string>
-    <string name="enable_search_history_summary">Hľadané výrazy ukladať lokálne</string>
+    <string name="enable_search_history_summary">Hľadané výrazy uloží lokálne</string>
     <string name="enable_watch_history_title">História pozretí</string>
-    <string name="enable_watch_history_summary">Ukladať históriu pozretých videí</string>
+    <string name="enable_watch_history_summary">Uloží históriu pozretých videí</string>
     <string name="resume_on_audio_focus_gain_title">Pokračovať v prehrávaní</string>
-    <string name="resume_on_audio_focus_gain_summary">Pokračovať po prerušeniach (napr. telefonát)</string>
+    <string name="resume_on_audio_focus_gain_summary">Pokračouje v prehrávaní po prerušení (napr. po telefonáte)</string>
     <string name="notification_channel_name">NewPipe oznámenia</string>
     <string name="notification_channel_description">Notifikácie NewPipe prehrávača</string>
     <string name="settings_category_downloads_title">Preberanie</string>
     <string name="settings_file_charset_title">Povolené znaky v názvoch súborov</string>
-    <string name="settings_file_replacement_character_summary">Neplatné znaky budú nahradené týmito znakmi</string>
+    <string name="settings_file_replacement_character_summary">Neplatné znaky budú nahradené znakmi s týmto významom</string>
     <string name="settings_file_replacement_character_title">Náhradný znak</string>
     <string name="charset_letters_and_digits">Písmená a číslice</string>
     <string name="charset_most_special_characters">Väčšina špeciálnych znakov</string>
@@ -163,12 +163,12 @@
         <item quantity="other">%s videí</item>
     </plurals>
     <string name="no_player_found_toast">Nebol nájdený žiadny prehrávač pre stream (môžete si nainštalovať napr. VLC).</string>
-    <string name="controls_download_desc">Stiahnuť súbor stream</string>
+    <string name="controls_download_desc">Stiahnuť súbor streamu</string>
     <string name="show_info">Zobraziť info</string>
     <string name="tab_bookmarks">Uložené zoznamy</string>
     <string name="controls_add_to_playlist_title">Pridať do</string>
     <string name="show_hold_to_append_title">Zobrazovať tip \"Pridať podržaním\"</string>
-    <string name="show_hold_to_append_summary">Zobraziť tip pre stlačenie tlačidiel \"Pozadie\" alebo \"V okne\" v detailoch videa</string>
+    <string name="show_hold_to_append_summary">Zobrazí tip pri stlačení tlačidiel pozadia alebo vyskakovacieho okna videa \"Podrobnosti:\"</string>
     <string name="default_content_country_title">Predvolený obsah pre krajinu</string>
     <string name="play_all">Prehrať všetko</string>
     <string name="always">Vždy</string>
@@ -179,8 +179,8 @@
     <string name="switch_to_main">Prepnúť na Video</string>
     <string name="import_data_title">Importovať databázu</string>
     <string name="export_data_title">Exportovať databázu</string>
-    <string name="import_data_summary">Prepíše aktuálnu históriu, odbery, zoznamy skladieb a (voliteľne aj) nastavenia</string>
-    <string name="export_data_summary">Exportovať históriu, odbery, zoznamy skladieb a nastavenia</string>
+    <string name="import_data_summary">Prepíše aktuálnu históriu, odbery, zoznamy skladieb a (voliteľne) nastavenia</string>
+    <string name="export_data_summary">Exportuje históriu, odbery, zoznamy skladieb a nastavenia</string>
     <string name="player_stream_failure">Nepodarilo sa prehrať tento stream</string>
     <string name="player_unrecoverable_failure">Pri prehrávaní došlo k chybe a nemožno pokračovať</string>
     <string name="player_recoverable_failure">Zotavovanie po chybe v prehrávaní</string>
@@ -247,7 +247,7 @@
     <string name="use_inexact_seek_summary">Rýchly posun umožňuje prejsť na novú pozíciu rýchlejšie, ale s menšou presnosťou. Posun o 5, 15 alebo 25 sekúnd v tomto prípade nie je možný</string>
     <string name="thumbnail_cache_wipe_complete_notice">Vyrovnávacia pamäť obrázkov vymazaná</string>
     <string name="metadata_cache_wipe_title">Vymazať metadáta uložené vo vyrovnávacej pamäti</string>
-    <string name="metadata_cache_wipe_summary">Odstrániť všetky údaje webových stránok vo vyrovnávacej pamäti</string>
+    <string name="metadata_cache_wipe_summary">Odstráni všetky údaje webových stránok vo vyrovnávacej pamäti</string>
     <string name="metadata_cache_wipe_complete_notice">Vyrovnávacia pamäť metadát bola vymazaná</string>
     <string name="auto_queue_title">Automaticky zaradiť ďalší stream</string>
     <string name="auto_queue_summary">Končiaci (neopakujúci sa) zoznam prehrávania bude pokračovať súvisiacim streamom</string>
@@ -264,9 +264,9 @@
     <string name="caption_auto_generated">Automaticky vygenerované</string>
     <string name="caption_setting_title">Titulky</string>
     <string name="caption_setting_description">Upravte mierku textu titulkov prehrávača a štýly pozadia. Vyžaduje sa reštart aplikácie</string>
-    <string name="enable_leak_canary_summary">Monitorovanie pretečenia pamäte môže spôsobiť, že aplikácia nebude reagovať</string>
+    <string name="enable_leak_canary_summary">Monitorovanie úniku pamäte môže spôsobiť, že aplikácia nebude reagovať</string>
     <string name="enable_disposed_exceptions_title">Nahlásiť mimo-cyklické chyby</string>
-    <string name="enable_disposed_exceptions_summary">Vynútiť hlásenie výnimiek nedoručiteľných Rx mimo časového cyklu fragmentov alebo aktivity po zneškodnení</string>
+    <string name="enable_disposed_exceptions_summary">Vynúti hlásenie nedoručiteľných výnimiek Rx mimo časového cyklu fragmentov alebo aktivity po zneškodnení</string>
     <string name="import_title">Import</string>
     <string name="import_from">Importovať z</string>
     <string name="export_to">Exportovať do</string>
@@ -304,7 +304,7 @@
     <string name="delete_view_history_alert">Vymazať celú históriu pozretí\?</string>
     <string name="watch_history_deleted">História pozretí bola vymazaná</string>
     <string name="clear_search_history_title">Vymazať históriu vyhľadávania</string>
-    <string name="clear_search_history_summary">Vymaže históriu vyhľadávania kľúčových slov</string>
+    <string name="clear_search_history_summary">Odstráni históriu vyhľadávania kľúčových slov</string>
     <string name="delete_search_history_alert">Vymazať celú históriu vyhľadávania\?</string>
     <string name="search_history_deleted">História vyhľadávaní bola vymazaná</string>
     <string name="one_item_deleted">1 položka bola vymazaná.</string>
@@ -328,7 +328,7 @@
     <string name="playback_step">Krok</string>
     <string name="playback_reset">Vynulovať</string>
     <string name="minimize_on_exit_title">Minimalizovať pri prepnutí aplikácie</string>
-    <string name="minimize_on_exit_summary">Akcia pri prepnutí na inú aplikáciu z hlavného prehrávača videa — %s</string>
+    <string name="minimize_on_exit_summary">Akcia pri prepnutí do inej aplikácie z hlavného prehrávača videa - %s</string>
     <string name="minimize_on_exit_none_description">Nič</string>
     <string name="minimize_on_exit_background_description">Prehrávať na pozadí</string>
     <string name="minimize_on_exit_popup_description">Prehrávať v okne</string>
@@ -345,7 +345,7 @@
     <string name="restore_defaults">Obnoviť predvolené nastavenia</string>
     <string name="restore_defaults_confirmation">Chcete obnoviť predvolené hodnoty\?</string>
     <string name="subscribers_count_not_available">Počet odberateľov nie je k dispozícii</string>
-    <string name="main_page_content_summary">Karty, ktoré sa zobrazujú na hlavnej stránke</string>
+    <string name="main_page_content_summary">Aké karty sa zobrazujú na hlavnej stránke</string>
     <string name="conferences">Konferencie</string>
     <string name="updates_setting_title">Aktualizácie</string>
     <string name="updates_setting_description">Zobrazí sa výzva na aktualizáciu aplikácie, keď je k dispozícii nová verzia</string>
@@ -379,7 +379,7 @@
     <string name="error_postprocessing_failed">Post-spracovanie zlyhalo</string>
     <string name="stop">Stop</string>
     <string name="max_retry_msg">Maximum opakovaní</string>
-    <string name="max_retry_desc">Maximálny počet pokusov pred zrušením stiahnutia</string>
+    <string name="max_retry_desc">Maximálny počet pokusov pred zrušením sťahovania</string>
     <string name="pause_downloads_on_mobile">Pozastaviť pri prechode na mobilné dáta</string>
     <string name="pause_downloads_on_mobile_desc">Je to užitočné pri prechode na mobilné dáta, niektoré sťahovania však nemožno pozastaviť</string>
     <string name="show_comments_title">Zobraziť komentáre</string>
@@ -389,9 +389,9 @@
     <string name="error_unable_to_load_comments">Nemožno načítať komentáre</string>
     <string name="close">Zatvoriť</string>
     <string name="enable_playback_resume_title">Obnoviť prehrávanie</string>
-    <string name="enable_playback_resume_summary">Obnovenie poslednej pozície prehrávania</string>
+    <string name="enable_playback_resume_summary">Obnoví poslednú pozíciu prehrávania</string>
     <string name="enable_playback_state_lists_title">Poradie v zoznamoch</string>
-    <string name="enable_playback_state_lists_summary">Zobraziť indikátory polohy prehrávania v zoznamoch</string>
+    <string name="enable_playback_state_lists_summary">Zobrazí indikátory pozície prehrávania v zoznamoch</string>
     <string name="settings_category_clear_data_title">Vymazať údaje</string>
     <string name="watch_history_states_deleted">Pozície prehrávania boli odstránené</string>
     <string name="missing_file">Súbor bol presunutý alebo odstránený</string>
@@ -477,7 +477,7 @@
         <item quantity="other">%d dní</item>
     </plurals>
     <string name="feed_groups_header_title">Skupiny kanálov</string>
-    <string name="feed_oldest_subscription_update">Aktualizované: %s</string>
+    <string name="feed_oldest_subscription_update">Zdroj naposledy aktualizovaný: %s</string>
     <string name="feed_subscription_not_loaded_count">Nenačítané: %d</string>
     <string name="feed_notification_loading">Načítavanie zdroja…</string>
     <string name="feed_processing_message">Spracovávanie zdroja…</string>
@@ -493,7 +493,7 @@
     <string name="feed_create_new_group_button_title">Nová</string>
     <string name="settings_category_feed_title">Zdroj</string>
     <string name="feed_update_threshold_title">Interval obnovy zdroja</string>
-    <string name="feed_update_threshold_summary">Čas po ktorom sa kontrola odberu považuje za zastaralú — %s</string>
+    <string name="feed_update_threshold_summary">Čas od poslednej aktualizácie, kedy sa odber považuje za neaktuálny - %s</string>
     <string name="feed_update_threshold_option_always_update">Vždy aktualizovať</string>
     <string name="feed_use_dedicated_fetch_method_title">Načítať z vyhradeného zdroja, ak je k dispozícii</string>
     <string name="feed_use_dedicated_fetch_method_summary">Služba je dostupná len pre určité zdroje, zvyčajne je rýchlejšia ale môže vrátiť obmedzený počet položiek alebo neúplné informácie (napr. dĺžka, typ, status)</string>
@@ -539,7 +539,7 @@
     <string name="feed_group_show_only_ungrouped_subscriptions">Zobraziť iba nezoskupené odbery</string>
     <string name="never">Nikdy</string>
     <string name="wifi_only">Iba na WiFi</string>
-    <string name="autoplay_summary">Spustiť automatické prehrávanie — %s</string>
+    <string name="autoplay_summary">Spustí automatické prehrávanie - %s</string>
     <string name="title_activity_play_queue">Prehrať zoznam</string>
     <string name="no_playlist_bookmarked_yet">Zatiaľ bez záložiek zoznamu</string>
     <string name="select_a_playlist">Vyberte zoznam skladieb</string>
@@ -547,38 +547,38 @@
     <string name="unsupported_url_dialog_message">Nemožno rozpoznať URL. Otvoriť pomocou inej aplikácie\?</string>
     <string name="auto_queue_toggle">Automatický rad</string>
     <string name="clear_queue_confirmation_description">Zoznam aktuálneho prehrávača bude prepísaný</string>
-    <string name="clear_queue_confirmation_summary">Prepnutie prehrávača môže vymazať zoznam</string>
+    <string name="clear_queue_confirmation_summary">Prepnutie z jedného prehrávača na druhý môže nahradiť vaše poradie</string>
     <string name="clear_queue_confirmation_title">Vyžadovať povolenie pred vyčistením zoznamu</string>
     <string name="notification_action_nothing">Nič</string>
     <string name="notification_action_buffering">Bufferuje</string>
     <string name="notification_action_shuffle">Náhodné</string>
     <string name="notification_action_repeat">Opakovať</string>
     <string name="notification_actions_at_most_three">V kompaktnom oznámení môžete vybrať najviac tri akcie, ktoré sa majú zobraziť!</string>
-    <string name="notification_actions_summary">Všetky nasledovné upozornenia môžete upraviť poklepaním. Tri z nich môžete vybrať a tieto sa budú zobrazovať v kompaktnom oznámení</string>
+    <string name="notification_actions_summary">Všetky nasledovné oznámenia môžete upraviť poklepaním. Tri z nich môžete vybrať a tieto sa budú zobrazovať v kompaktnom oznámení.</string>
     <string name="notification_action_4_title">Akčné tlačidlo päť</string>
     <string name="notification_action_3_title">Akčné tlačidlo štyri</string>
     <string name="notification_action_2_title">Akčné tlačidlo tri</string>
     <string name="notification_action_1_title">Akčné tlačidlo dva</string>
     <string name="notification_action_0_title">Akčné tlačidlo jedna</string>
-    <string name="notification_scale_to_square_image_summary">Orezať pomer strán videa zobrazovaného v miniatúre z 16:9 na 1:1</string>
+    <string name="notification_scale_to_square_image_summary">Oreže miniatúru videa zobrazenej v oznámení z pomeru strán 16:9 na 1:1</string>
     <string name="notification_scale_to_square_image_title">Orezať pomer strán miniatúry na 1: 1</string>
     <string name="show_memory_leaks">Zobraziť memory leaks</string>
     <string name="enqueued">Zaradené do poradia</string>
     <string name="enqueue_stream">Zaradiť do poradia</string>
-    <string name="clear_cookie_summary">Vymazať cookies, ktoré NewPipe ukladá, keď vyriešite reCAPTCHA</string>
+    <string name="clear_cookie_summary">Vymaže cookies, ktoré NewPipe ukladá, keď vyriešite reCAPTCHA</string>
     <string name="recaptcha_cookies_cleared">reCAPTCHA cookies boli vymazané</string>
     <string name="clear_cookie_title">Vymazať cookies reCAPTCHA</string>
     <string name="youtube_restricted_mode_enabled_summary">YouTube poskytuje \"Obmedzený režim\", ktorý skrýva potenciálny obsah pre dospelých</string>
-    <string name="show_age_restricted_content_summary">Zobraziť obsah, ktorý je možno nevhodný pre deti, pretože má vekovú hranicu (napríklad 18+)</string>
-    <string name="notification_colorize_summary">Nechajte Android, aby prispôsobil farbu upozornenia podľa hlavnej farby v miniatúre (nemusí to fungovať na všetkých zariadeniach)</string>
+    <string name="show_age_restricted_content_summary">Zobrazí obsah, ktorý je pravdepodobne nevhodný pre deti, pretože má vekové obmedzenie (napríklad 18+)</string>
+    <string name="notification_colorize_summary">Nechajte Android, aby prispôsobil farbu oznámenia podľa hlavnej farby v miniatúre (nemusí to fungovať na všetkých zariadeniach)</string>
     <string name="notification_colorize_title">Farby upozornení</string>
     <string name="show_thumbnail_title">Zobrazovať miniatúru</string>
-    <string name="show_thumbnail_summary">Používať miniatúru ako pozadie pri uzamknutej obrazovke a v upozorneniach</string>
+    <string name="show_thumbnail_summary">Použije miniatúru pre pozadie uzamknutej obrazovke a oznámenia</string>
     <string name="msg_calculating_hash">Počítanie hash</string>
-    <string name="hash_channel_description">Upozornenie pri generovaní hash z názu videa</string>
+    <string name="hash_channel_description">Oznámenia o priebehu hašovania videa</string>
     <string name="hash_channel_name">Oznámenie o hashovaní videa</string>
     <string name="recent">Nedávne</string>
-    <string name="show_meta_info_summary">Vypnite, pokiaľ nechcete zobrazovať meta informácie ako autor streamu, obsah streamu alebo vyhľadávanie</string>
+    <string name="show_meta_info_summary">Vypnutím skryjete metainformačné polia s ďalšími informáciami o autorovi streamu, obsahu streamu alebo požiadavke na vyhľadávanie</string>
     <string name="show_meta_info_title">Zobrazovať meta informácie</string>
     <string name="chapters">Kapitoly</string>
     <string name="description_tab_description">Popis</string>
@@ -605,7 +605,7 @@
     <string name="show_channel_details">Zobraziť podrobnosti kanála</string>
     <string name="recaptcha_solve">Vyrieš</string>
     <string name="night_theme_title">Nočná téma</string>
-    <string name="disable_media_tunneling_summary">Ak vám video pri prehrávaní seká alebo sa zobrazuje čierna obrazovka zakážte tunelovanie médií</string>
+    <string name="disable_media_tunneling_summary">Ak sa video pri prehrávaní seká alebo sa objaví čierna obrazovka, vypnite tunelovanie médií.</string>
     <string name="disable_media_tunneling_title">Zakázať tunelovanie médií</string>
     <string name="metadata_privacy_internal">Interné</string>
     <string name="metadata_privacy_private">Súkromné</string>
@@ -631,8 +631,8 @@
 \nChcete sa odhlásiť z odberu tohto kanála\?</string>
     <string name="feed_load_error_account_info">Nemožno načítať informačný kanál \'%s\'.</string>
     <string name="feed_load_error">Chyba pri načítaní kanála</string>
-    <string name="downloads_storage_use_saf_summary_api_29">\'Storage Access Framework\' je podporovaný len od Androidu 10 a vyššie</string>
-    <string name="downloads_storage_ask_summary_no_saf_notice">Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor</string>
+    <string name="downloads_storage_use_saf_summary_api_29">Od Androidu 10 je podporovaný len \'Storage Access Framework\'</string>
+    <string name="downloads_storage_ask_summary_no_saf_notice">Pri každom sťahovaní sa zobrazí výzva, kam uložiť súbor</string>
     <string name="no_dir_yet">Nie je nastavený adresár na sťahovanie, nastavte ho teraz</string>
     <string name="mark_as_watched">Označiť ako videné</string>
     <string name="off">Vypnuté</string>
@@ -653,11 +653,11 @@
     <string name="low_quality_smaller">Nízka kvalita (menšie)</string>
     <string name="high_quality_larger">Vysoká kvalita (väčšie)</string>
     <string name="seekbar_preview_thumbnail_title">Náhľad miniatúry pri vyhľadávaní</string>
-    <string name="show_image_indicators_summary">Zobraziť farebné stužky Picassa na obrázkoch podľa ich zdroja: červená pre sieť, modrá pre disk a zelená pre pamäť</string>
+    <string name="show_image_indicators_summary">Zobrazí farebné pásiky Picasso na obrázkoch podľa ich zdroja: červený pre sieť, modrý pre disk a zelený pre pamäť</string>
     <string name="show_image_indicators_title">Zobraziť indikátory obrázka</string>
     <string name="main_page_content_swipe_remove">Potiahnutím vymazať</string>
     <string name="comments_are_disabled">Komentáre sú zakázané</string>
-    <string name="start_main_player_fullscreen_summary">Pri zamknutej auto-rotácií nespúšťať videá v mini prehrávači, ale prepnúť sa priamo do režimu celej obrazovky. Prístup k mini prehrávaču bude po ukončení režimu celej obrazovky</string>
+    <string name="start_main_player_fullscreen_summary">Ak je automatické otáčanie zablokované, nespustí videá v miniprehrávači, ale prepne sa do celoobrazovkového režimu. Do miniprehrávača sa dostanete po ukončení režimu celej obrazovky</string>
     <string name="start_main_player_fullscreen_title">Hlavný prehrávač na celej obrazovke</string>
     <string name="remote_search_suggestions">Návrhy vzdialeného vyhľadávania</string>
     <string name="local_search_suggestions">Miestne návrhy vyhľadávania</string>
@@ -689,13 +689,13 @@
     <string name="delete_downloaded_files_confirm">Vymazať všetky stiahnuté súbory z disku\?</string>
     <string name="notifications_disabled">Upozornenia sú vypnuté</string>
     <string name="settings_category_player_notification_title">Upozornenie z prehrávača</string>
-    <string name="settings_category_player_notification_summary">Nastavte notifikáciu aktuálneho prehrávania</string>
+    <string name="settings_category_player_notification_summary">Nastavte oznámenie aktuálneho streamu prehrávania</string>
     <string name="streams_notifications_network_title">Je vyžadované pripojenie na internet</string>
     <string name="you_successfully_subscribed">Začali ste odoberať tento kanál</string>
     <string name="enumeration_comma">,</string>
     <string name="toggle_all">Zapnúť všetko</string>
     <string name="streams_notification_channel_name">Nové streamy</string>
-    <string name="streams_notification_channel_description">Upozornenia na nové streamy v odberoch</string>
+    <string name="streams_notification_channel_description">Oznámenia o nových streamoch v odberoch</string>
     <plurals name="new_streams">
         <item quantity="one">%s nový stream</item>
         <item quantity="few">%s nové streamy</item>
@@ -703,7 +703,7 @@
     </plurals>
     <string name="check_new_streams">Skontrolovať nové streamy</string>
     <string name="enable_streams_notifications_title">Upozornenia na nové streamy</string>
-    <string name="enable_streams_notifications_summary">Upozorniť na nové streamy z odberov</string>
+    <string name="enable_streams_notifications_summary">Upozorní na nové streamy z odberov</string>
     <string name="any_network">Akákoľvek sieť</string>
     <string name="get_notified">Dostávať upozornenia</string>
     <string name="semitone">Poltón</string>
@@ -732,9 +732,9 @@
     <string name="fast_mode">Rýchly režim</string>
     <string name="import_subscriptions_hint">Import alebo export odberov z 3-bodkovej ponuky</string>
     <string name="left_gesture_control_title">Akcia gesta vľavo</string>
-    <string name="right_gesture_control_summary">Akcia pre gesto v pravej polovici obrazovky</string>
+    <string name="right_gesture_control_summary">Vyberte gesto pre pravú polovicu obrazovky prehrávača</string>
     <string name="right_gesture_control_title">Akcia gesta vpravo</string>
-    <string name="left_gesture_control_summary">Akcia pre gesto v ľavej polovici obrazovky</string>
+    <string name="left_gesture_control_summary">Vyberte gesto pre ľavú polovicu obrazovky prehrávača</string>
     <string name="brightness">Jas</string>
     <string name="volume">Hlasitosť</string>
     <string name="none">Nič</string>
@@ -742,11 +742,11 @@
     <string name="remove_duplicates_title">Odstrániť duplikáty\?</string>
     <string name="feed_show_partially_watched">Rozpozerané</string>
     <string name="feed_show_watched">Dopozerané</string>
-    <string name="ignore_hardware_media_buttons_summary">Toto sa môže hodiť v prípade, že máte pokazené tlačidlo na slúchadlách</string>
+    <string name="ignore_hardware_media_buttons_summary">Toto sa môže hodiť v prípade, že máte pokazené tlačidlá na slúchadlách</string>
     <string name="ignore_hardware_media_buttons_title">Ignorovať fyzické tlačidlá</string>
-    <string name="progressive_load_interval_summary">Zmena veľkosti intervalu načítania pri progresívnom obsahu (v súčasnosti %s). Nižšia hodnota môže urýchliť ich počiatočné načítanie</string>
+    <string name="progressive_load_interval_summary">Zmení veľkosť intervalu načítania pri progresívnom obsahu (v súčasnosti %s). Nižšia hodnota môže urýchliť ich počiatočné načítanie</string>
     <string name="prefer_original_audio_title">Preferovať pôvodné audio</string>
-    <string name="prefer_original_audio_summary">Vyberať pôvodnú zvukový stopu bez ohľadu na nastavený jazyk</string>
+    <string name="prefer_original_audio_summary">Vyberte pôvodnú zvukovú stopu bez ohľadu na jazyk</string>
     <string name="prefer_descriptive_audio_title">Preferovať prehrávanie popisu</string>
     <string name="play_queue_audio_track">Zvuk: %s</string>
     <string name="audio_track">Zvuková stopa</string>
@@ -756,41 +756,41 @@
     <string name="select_audio_track_external_players">Výber zvukovej stopy pre externé prehrávače</string>
     <string name="unknown_audio_track">Neznáma</string>
     <string name="feed_show_upcoming">Nadchádzajúce</string>
-    <string name="settings_category_exoplayer_summary">Úprava nastavení prehrávača ExoPlayer. Pre aplikovanie týchto zmien je nutné reštartovať ExoPlayer</string>
+    <string name="settings_category_exoplayer_summary">Úprava niektorých nastavení prehrávača ExoPlayer. Pre aplikovanie týchto zmien je potrebné reštartovať prehrávač</string>
     <string name="use_exoplayer_decoder_fallback_summary">Túto možnosť povoľte, ak máte problémy s inicializáciou dekodéra, ktorý sa v prípade zlyhania inicializácie primárnych dekodérov vráti k dekodérom s nižšou prioritou. To môže mať za následok nižší výkon prehrávania ako pri použití primárnych dekodérov</string>
-    <string name="use_exoplayer_decoder_fallback_title">Používať funkciu záložného dekodéra prehrávača ExoPlayer</string>
+    <string name="use_exoplayer_decoder_fallback_title">Použiť funkciu záložného dekodéra prehrávača ExoPlayer</string>
     <string name="always_use_exoplayer_set_output_surface_workaround_title">Vždy použiť nastavenia výstupu videa ExoPlayera</string>
     <string name="audio_track_name">%1$s %2$s</string>
     <string name="audio_track_type_original">povodná</string>
     <string name="audio_track_type_dubbed">dabovaná</string>
-    <string name="prefer_descriptive_audio_summary">Vyberať zvukovú stopu s popisom pre zrakovo postihnutých ak je k dispozicií</string>
+    <string name="prefer_descriptive_audio_summary">Vyberte zvukovú stopu s popisom pre osoby so zrakovým postihnutím, ak je k dispozícii</string>
     <string name="feed_show_hide_streams">Zobrazovať/Skrývať streamy</string>
     <string name="settings_category_exoplayer_title">Nastavenia ExoPlayer</string>
-    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Toto riešenie spôsobí to, že pri zmene textúry sa aktuálne používaný kodek uvoľní a potom znova použije, miesto toho aby sa textúra rovno použila na kodek. Toto nastavenie, ktoré už ExoPlayer používa na niektorých zariadeniach s týmto problémom, má vplyv len na Android 6 a vyšší
+    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Toto riešenie uvoľní a znovu nastaví videokodeky pri zmene rozhrania namiesto priameho nastavenia rozhrania kodeku. Nastavenie už používa ExoPlayer na niektorých zariadeniach s týmto problémom a týka sa len systému Android 6 a vyššie
 \n
-\nPovolenie tejto možnosti môže zabrániť chybám pri prehrávaní pri prepínaní aktuálneho prehrávača videa alebo prepínaní na celú obrazovku</string>
+\nPovolením tejto možnosti môžete zabrániť chybám prehrávania pri zmene aktuálneho prehrávača videa alebo pri prepnutí do režimu celej obrazovky</string>
     <string name="audio_track_type_descriptive">popisná</string>
     <string name="channel_tab_videos">Videá</string>
     <string name="metadata_subscribers">Odberatelia</string>
-    <string name="show_channel_tabs_summary">Karty zobrazované na stránkach kanála</string>
+    <string name="show_channel_tabs_summary">Aké karty sa zobrazujú na stránkach kanála</string>
     <string name="show_channel_tabs">Karty kanálov</string>
     <string name="channel_tab_shorts">Krátke</string>
     <string name="loading_metadata_title">Načítavanie metadát…</string>
     <string name="toggle_screen_orientation">Prepnúť orientáciu obrazovky</string>
-    <string name="main_tabs_position_title">Pozícia hlavných kariet</string>
+    <string name="main_tabs_position_title">Poloha hlavných kariet</string>
     <string name="toggle_fullscreen">Prepnúť na celú obrazovku</string>
     <string name="feed_fetch_channel_tabs">Načítať karty kanála</string>
-    <string name="next_stream">Ďalší prúd</string>
+    <string name="next_stream">Ďalší stream</string>
     <string name="disable_media_tunneling_automatic_info">Vaše zariadenie nepodporuje tunelovanie medií a preto bolo tunelovanie medií zakázané.</string>
     <string name="open_play_queue">Otvoriť zoznam prehrávania</string>
     <string name="channel_tab_about">O kanále</string>
     <string name="forward">Dopredu</string>
     <string name="channel_tab_albums">Albumy</string>
     <string name="rewind">Dozadu</string>
-    <string name="replay">Opakovať</string>
-    <string name="feed_fetch_channel_tabs_summary">Karty na načítanie pri aktualizácii informačného kanála. Táto možnosť nemá žiadny účinok, ak je kanál aktualizovaný pomocou rýchleho režimu.</string>
+    <string name="replay">Opäť prehrať</string>
+    <string name="feed_fetch_channel_tabs_summary">Karty, ktoré sa majú načítať pri aktualizácii informačného kanála. Táto možnosť nemá žiadny účinok, ak je kanál aktualizovaný pomocou rýchleho režimu.</string>
     <string name="channel_tab_playlists">Zoznamy skladieb</string>
-    <string name="main_tabs_position_summary">Presunúť lištu kariet dolu</string>
+    <string name="main_tabs_position_summary">Presunie výber hlavnej karty do spodnej časti</string>
     <string name="no_live_streams">Žiadne živé prenosy</string>
     <string name="play">Prehrať</string>
     <string name="more_options">Ďalšie možnosti</string>
@@ -798,6 +798,33 @@
     <string name="duration">Dĺžka</string>
     <string name="no_streams">Žiadne videá</string>
     <string name="channel_tab_channels">Kanály</string>
-    <string name="previous_stream">Predchádzajúci prúd</string>
+    <string name="previous_stream">Predchádzajúci stream</string>
     <string name="channel_tab_livestreams">Naživo</string>
+    <string name="image_quality_title">Kvalita obrázkov</string>
+    <string name="question_mark">\?</string>
+    <string name="image_quality_low">Nízka kvalita</string>
+    <string name="metadata_avatars">Avatary</string>
+    <string name="image_quality_none">Nenačítavať obrázky</string>
+    <string name="image_quality_high">Vysoká kvalita</string>
+    <string name="share_playlist">Zdieľať playlist</string>
+    <string name="image_quality_medium">Stredná kvalita</string>
+    <string name="metadata_banners">Bannery</string>
+    <string name="image_quality_summary">Vyberte kvalitu obrázkov a či sa majú obrázky vôbec načítať, aby sa znížila spotreba dát a pamäte. Zmeny vymažú vyrovnávaciu pamäť obrázkov v pamäti a na disku - %s</string>
+    <string name="show_more">Zobraziť viac</string>
+    <string name="show_less">Zobraziť menej</string>
+    <string name="notification_actions_summary_android13">Každú akciu oznámenia upravte poklepaním. Prvé tri akcie (prehrať/pozastaviť, predchádzajúce, ďalšie) sú nastavené systémom a nie je možné ich prispôsobiť.</string>
+    <string name="metadata_thumbnails">Náhľady</string>
+    <string name="metadata_uploader_avatars">Avatary uploadera</string>
+    <string name="metadata_subchannel_avatars">Sub-kanálové avatary</string>
+    <string name="share_playlist_with_titles">Zdieľať s názvami</string>
+    <string name="share_playlist_with_list">Zdieľať URL playlistu</string>
+    <plurals name="replies">
+        <item quantity="one">%s odpoveď</item>
+        <item quantity="few">%s odpovede</item>
+        <item quantity="other">%s odpovedí</item>
+    </plurals>
+    <string name="share_playlist_with_titles_message">Zdieľajte playlist s podrobnosťami, ako je jeho názov a názvy videí, alebo ako jednoduchý zoznam URL adries videí</string>
+    <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="share_playlist_content_details">%1$s
+\n%2$s</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 8bb8d1b3d..66659c77a 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -227,7 +227,7 @@
     <string name="enable_queue_limit">Ограничи ред за преузимање</string>
     <string name="close">Затвори</string>
     <string name="pause_downloads_on_mobile_desc">Корисно при преласку на мобилне податке, иако нека преузимања не могу бити обустављена</string>
-    <string name="pause_downloads_on_mobile">Прекид на мереним мрежама</string>
+    <string name="pause_downloads_on_mobile">Прекид на мрежама с ограничењем</string>
     <string name="max_retry_desc">Највећи број покушаја пре отказивања преузимања</string>
     <string name="max_retry_msg">Највише покушаја</string>
     <string name="stop">Заустави</string>
@@ -280,7 +280,7 @@
     <string name="minimize_on_exit_title">Умањи при мењању апликације</string>
     <string name="updates_setting_description">Прикажи обавештење када постоји нова верзија апликације</string>
     <string name="updates_setting_title">Ажурирања</string>
-    <string name="limit_mobile_data_usage_title">Ограничи резолуцију када користиш мобилне податке</string>
+    <string name="limit_mobile_data_usage_title">Ограничење резолуције када се користе мобилни подаци</string>
     <string name="decline">Одбиј</string>
     <string name="limit_data_usage_none_description">Без ограничења</string>
     <string name="accept">Прихвати</string>
@@ -448,7 +448,7 @@
     <string name="clear_views_history_title">Очисти историју гледања</string>
     <string name="clear_cookie_summary">Чисти колачиће које NewPipe чува када решите „reCAPTCHA“</string>
     <string name="export_data_summary">Извоз историје, праћења, плејлисти и подешавања</string>
-    <string name="import_data_summary">Замениће вашу тренутну историју, праћења, плејлисте и (опционо) подешавања</string>
+    <string name="import_data_summary">Замениће вашу тренутну историју, праћења, плејлисте и (опционално) подешавања</string>
     <string name="recaptcha_cookies_cleared">„reCAPTCHA“ колачићи су очишћени</string>
     <string name="clear_cookie_title">Очисти „reCAPTCHA“ колачиће</string>
     <string name="export_data_title">Извоз базе података</string>
@@ -485,7 +485,7 @@
     <string name="peertube_instance_add_fail">Није могуће потврдити инстанцу</string>
     <string name="peertube_instance_add_help">Унесите URL адресу инстанце</string>
     <string name="peertube_instance_add_title">Додајте инстанцу</string>
-    <string name="peertube_instance_url_help">Пронађите инстанце које Вам се свиђају на %s</string>
+    <string name="peertube_instance_url_help">Пронађите инстанце које вам се свиђају на %s</string>
     <string name="peertube_instance_url_title">PeerTube инстанце</string>
     <string name="peertube_instance_url_summary">Изаберите своје омиљене PeerTube инстанце</string>
     <string name="unsupported_url_dialog_message">URL адреса није препозната. Отворити помоћу друге апликације\?</string>
@@ -521,7 +521,7 @@
     <string name="notification_action_2_title">Дугме треће радње</string>
     <string name="notification_action_1_title">Дугме друге радње</string>
     <string name="notification_action_0_title">Дугме прве радње</string>
-    <string name="notification_scale_to_square_image_summary">Исеците сличицу видеа приказану у обавештењу с размере 16:9 на размеру 1:1</string>
+    <string name="notification_scale_to_square_image_summary">Исеците сличицу видео снимка приказану у обавештењу с размере 16:9 на размеру 1:1</string>
     <string name="notification_scale_to_square_image_title">Исеци сличицу на размеру 1:1</string>
     <string name="search_showing_result_for">Приказивање резултата за: %s</string>
     <string name="open_with">Отвори помоћу</string>
@@ -572,9 +572,9 @@
     <string name="feed_group_dialog_delete_message">Желите ли да избришете ову групу\?</string>
     <string name="feed_group_dialog_empty_name">Празан назив групе</string>
     <plurals name="feed_group_dialog_selection_count">
-        <item quantity="one">%d одабран</item>
-        <item quantity="few">%d одабрана</item>
-        <item quantity="other">%d одабраних</item>
+        <item quantity="one">%d изабран</item>
+        <item quantity="few">%d изабрана</item>
+        <item quantity="other">%d изабраних</item>
     </plurals>
     <string name="feed_group_dialog_empty_selection">Нема изабраних праћења</string>
     <string name="feed_group_dialog_select_subscriptions">Изабрана праћења</string>
@@ -719,7 +719,7 @@
     <string name="seekbar_preview_thumbnail_title">Преглед сличице траке за претрагу</string>
     <string name="feed_hide_streams_title">Прикажи следеће стримове</string>
     <string name="feed_show_hide_streams">Прикажи/сакриј стримове</string>
-    <string name="use_exoplayer_decoder_fallback_title">Користите резервну функцију декодера ExoPlayer-а</string>
+    <string name="use_exoplayer_decoder_fallback_title">Користи резервну функцију декодера ExoPlayer-а</string>
     <string name="audio_track_type_original">оригинални</string>
     <string name="progressive_load_interval_summary">Промените величину интервала учитавања на прогресивним садржајима (тренутно %s). Нижа вредност може убрзати њихово почетно учитавање</string>
     <string name="ignore_hardware_media_buttons_summary">Корисно, на пример, ако користите слушалице са поквареним физичким дугмадима</string>
@@ -731,7 +731,7 @@
     <string name="settings_category_player_notification_summary">Конфигуришите обавештење о тренутно репродукованом стриму</string>
     <string name="prefer_original_audio_summary">Изаберите оригинални аудио снимак, без обзира на језик</string>
     <string name="prefer_descriptive_audio_summary">Изаберите аудио снимак са описима за особе са оштећеним видом, ако је доступан</string>
-    <string name="prefer_descriptive_audio_title">Дај предност описном аудио снимку</string>
+    <string name="prefer_descriptive_audio_title">Преферирај описни аудио снимак</string>
     <string name="right_gesture_control_title">Радња покретом удесно</string>
     <string name="brightness">Осветљеност</string>
     <string name="volume">Јачина звука</string>
@@ -764,7 +764,7 @@
     <string name="audio_track_type_descriptive">описни</string>
     <string name="audio_track_type_dubbed">синхронизовани</string>
     <string name="settings_category_exoplayer_summary">Управљајте неким подешавањима ExoPlayer-а. Ове промене захтевају рестартовање плејера да би се примениле</string>
-    <string name="prefer_original_audio_title">Дај предност оригиналном аудио снимку</string>
+    <string name="prefer_original_audio_title">Преферирај оригинални аудио снимак</string>
     <string name="always_use_exoplayer_set_output_surface_workaround_summary">Ово заобилазно решење ослобађа и поново инстанцира кодеке видео снимка када дође до промене површине, уместо да се површина директно поставља на кодек. ExoPlayer већ користи ово на неким уређајима са овим проблемом, ово подешавање утиче само на Android 6 и новије верзије.
 \n
 \nОмогућавање ове опције може спречити грешке при репродукцији приликом пребацивања тренутног плејера видео снимака или преласка на цео екран</string>
@@ -819,4 +819,12 @@
     <string name="share_playlist">Дели плејлисту</string>
     <string name="share_playlist_with_titles_message">Делите плејлисту са детаљима, као што су назив плејлисте и наслови видео снимака или као једноставна листа URL адреса видео снимака</string>
     <string name="video_details_list_item">-%1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="one">%s одговор</item>
+        <item quantity="few">%s одговора</item>
+        <item quantity="other">%s одговора</item>
+    </plurals>
+    <string name="show_more">Прикажи више</string>
+    <string name="show_less">Прикажи мање</string>
+    <string name="notification_actions_summary_android13">Измените сваку радњу обавештења у наставку тако што ћете је додирнути. Прве три радње (пусти/паузирај, претходни и следећи) поставља систем и не могу се прилагодити.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index d3739e29e..2f66aa0de 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -567,7 +567,7 @@
     <string name="show_description_summary">Inaktivera för att dölja videobeskrivning och ytterligare information</string>
     <string name="show_meta_info_title">Visa meta-information</string>
     <string name="show_thumbnail_summary">Använd miniatyrbild som bakgrund till både låsskärm och aviseringar</string>
-    <string name="notification_actions_summary">Redigera varje aviseringsåtgärd nedan genom att trycka på den. Välj upp till tre av dem som ska visas i den kompakta aviseringen genom att använda kryssrutorna till höger</string>
+    <string name="notification_actions_summary">Redigera varje aviseringsåtgärd nedan genom att trycka på den. Välj upp till tre av dem som ska visas i den kompakta aviseringen genom att använda kryssrutorna till höger.</string>
     <string name="notification_colorize_summary">Låt Android färglägga varje avisering individuellt utifrån miniatyrbildens huvudsakliga färg (notera att detta inte är tillgängligt på alla enheter)</string>
     <string name="notification_colorize_title">Färglägg avisering</string>
     <string name="notification_actions_at_most_three">Du kan som mest välja tre åtgärder att visa i den kompakta aviseringen!</string>
@@ -806,4 +806,11 @@
     <string name="video_details_list_item">- %1$s: %2$s</string>
     <string name="share_playlist_with_titles_message">Dela spellistan med detaljer så som spellistans namn och video-titlarna eller som en enkel lista med URL till videorna</string>
     <string name="image_quality_summary">Välj bildkvalitet och om bilder överhuvudtaget ska laddas för att minska data och minnesanvändningen. Ändringar rensar både i minnet och bildcache på disk – %s</string>
+    <string name="show_more">Visa mer</string>
+    <plurals name="replies">
+        <item quantity="one">%s svar</item>
+        <item quantity="other">%s svar</item>
+    </plurals>
+    <string name="show_less">Visa mindre</string>
+    <string name="notification_actions_summary_android13">Redigera varje aviseringsåtgärd nedan genom att trycka på den. De tre första åtgärderna (spela/pausa, föregående och nästa) är satta av systemet och kan inte ändras.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 9b97445e1..73cc5a3da 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -145,8 +145,8 @@
         <item quantity="other">%s பார்வைகள்</item>
     </plurals>
     <plurals name="videos">
-        <item quantity="one">காணொளிகள்</item>
-        <item quantity="other">காணொளிகள்</item>
+        <item quantity="one">%sகாணொளிகள்</item>
+        <item quantity="other">%sகாணொளிகள்</item>
     </plurals>
     <string name="switch_to_main">முதற்பக்கத்துக்கு மாற்று</string>
     <string name="video_streams_empty">எந்த காணொலியும் இல்லை</string>
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index aaf1bf4cb..b2846823c 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -146,7 +146,7 @@
     <string name="start_main_player_fullscreen_title">పూర్తి స్క్రీన్‌లో ప్రధాన ప్లేయర్‌ని ప్రారంభించండి</string>
     <string name="settings_category_debug_title">డీబగ్ చేయండి</string>
     <string name="notification_action_nothing">ఏమిలేదు</string>
-    <string name="notification_scale_to_square_image_summary">నోటిఫికేషన్‌లో చూపబడిన వీడియో థంబ్‌నెయిల్‌ను 16:9 నుండి 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి (వక్రీకరణలను ప్రవేశపెట్టవచ్చు)</string>
+    <string name="notification_scale_to_square_image_summary">నోటిఫికేషన్‌లో చూపబడిన వీడియో థంబ్‌నెయిల్‌ను 16:9 నుండి 1:1 కారక నిష్పత్తికి మార్చుము</string>
     <string name="notification_action_repeat">పునరావృతం చేయండి</string>
     <string name="show_hold_to_append_summary">వీడియో \"వివరాలు:\"లో బ్యాక్‌గ్రౌండ్ లేదా పాప్‌అప్ బటన్‌ను నొక్కినప్పుడు చిట్కాను చూపు</string>
     <string name="unsupported_url_dialog_message">URLని గుర్తించడం సాధ్యపడలేదు. మరొక యాప్‌తో తెరవాలా\?</string>
@@ -177,7 +177,7 @@
     <string name="tab_choose">ట్యాబ్‌ని ఎంచుకోండి</string>
     <string name="unsubscribe">సభ్యత్వాన్ని తొలగించుము</string>
     <string name="show_info">సమాచారాన్ని చూపుము</string>
-    <string name="notification_scale_to_square_image_title">సూక్ష్మచిత్రాన్ని 1:1 కారక నిష్పత్తికి స్కేల్ చేయండి</string>
+    <string name="notification_scale_to_square_image_title">సూక్ష్మచిత్రాన్ని 1:1 కారక నిష్పత్తికి మార్చుము</string>
     <string name="clear_queue_confirmation_summary">ఒక ప్లేయర్ నుండి మరొక ప్లేయర్‌కు మారడం వలన మీ క్యూను భర్తీ చేయవచ్చు</string>
     <string name="auto_queue_summary">సంబంధిత స్ట్రీమ్‌ను జోడించడం ద్వారా (పునరావృతం కాని) ప్లేబ్యాక్ క్యూను ముగించడాన్ని కొనసాగించండి</string>
     <string name="notification_actions_summary">దిగువన ఉన్న ప్రతి నోటిఫికేషన్ చర్యను దానిపై నొక్కడం ద్వారా సవరించండి. కుడివైపు ఉన్న చెక్‌బాక్స్‌లను ఉపయోగించడం ద్వారా కాంపాక్ట్ నోటిఫికేషన్‌లో చూపబడే వాటిలో మూడు వరకు ఎంచుకోండి</string>
@@ -201,7 +201,7 @@
     <string name="notification_action_shuffle">షఫుల్ చేయండి</string>
     <string name="notification_action_buffering">బఫరింగ్</string>
     <string name="default_video_format_title">డిఫాల్ట్ వీడియో ఫార్మాట్</string>
-    <string name="night_theme_title">రాత్రి థీమ్</string>
+    <string name="night_theme_title">చీకటి థీమ్</string>
     <string name="dark_theme_title">చీకటి</string>
     <string name="seek_duration_title">ఫాస్ట్-ఫార్వర్డ్/-రివైండ్ సీక్ వ్యవధి</string>
     <string name="clear_queue_confirmation_title">క్యూను క్లియర్ చేయడానికి ముందు నిర్ధారణ కోసం అడగండి</string>
@@ -462,4 +462,11 @@
     <string name="paused">ఆగిపోయింది</string>
     <string name="sort">క్రమం</string>
     <string name="app_update_available_notification_text">%sని డౌన్‌లోడ్ చేయడానికి నొక్కండి</string>
+    <string name="notification_actions_summary_android13">దిగువన ఉన్న ప్రతి నోటిఫికేషన్ చర్యను దానిపై నొక్కడం ద్వారా సవరించండి. మొదటి మూడు చర్యలు (ప్లే/పాజ్, మునుపటి మరియు తదుపరి) సిస్టమ్ ద్వారా సెట్ చేయబడ్డాయి మరియు అనుకూలీకరించబడవు.</string>
+    <string name="prefer_original_audio_summary">భాషతో సంబంధం లేకుండా అసలు ఆడియో ట్రాక్‌ని ఎంచుకోండి</string>
+    <string name="prefer_descriptive_audio_title">వివరణాత్మక ఆడియోకు ప్రాధాన్యత ఇవ్వండి</string>
+    <string name="ignore_hardware_media_buttons_summary">ఉదాహరణకు, మీరు విరిగిన భౌతిక బటన్‌లతో హెడ్‌సెట్‌ని ఉపయోగిస్తుంటే ఉపయోగకరంగా ఉంటుంది</string>
+    <string name="progressive_load_interval_summary">ప్రగతిశీల విషయాలపై లోడ్ విరామం పరిమాణాన్ని మార్చండి (ప్రస్తుతం %s). తక్కువ విలువ వాటి ప్రారంభ లోడింగ్‌ను వేగవంతం చేయవచ్చు</string>
+    <string name="ignore_hardware_media_buttons_title">హార్డ్‌వేర్ మీడియా బటన్ ఈవెంట్‌లను విస్మరించండి</string>
+    <string name="prefer_original_audio_title">అసలు శ్రావ్యయానికి ప్రాధాన్యత ఇవ్వండి</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ti/strings.xml b/app/src/main/res/values-ti/strings.xml
index ab3505453..7845242c2 100644
--- a/app/src/main/res/values-ti/strings.xml
+++ b/app/src/main/res/values-ti/strings.xml
@@ -47,7 +47,7 @@
     <string name="controls_popup_title">ፖፕኣፕ</string>
     <string name="default_resolution_title">ትሑዝ ድንቀት</string>
     <string name="download_path_summary">ዝወረዱ ናይ ተንቃሳቀሴ-ምስሌ ፋይላት ኣብዚ ይኽዘኑ</string>
-    <string name="default_video_format_title"></string>
+    <string name="default_video_format_title"/>
     <string name="notification_scale_to_square_image_title">ናይ እኽሊ ምስሊ ምስ ናይ 1:1 ርክባት</string>
     <string name="notification_action_0_title">ቀዳማይ ወሰን ተጠዋቃ</string>
     <string name="notification_action_repeat">ደገመ</string>
@@ -62,4 +62,9 @@
     <string name="download_path_title">ተንቃሳቀሴ-ምስሌ ፋይል ኣራግፍ</string>
     <string name="tab_choose">ታብ ምረጽ</string>
     <string name="controls_background_title">ድሕረ ባይታ</string>
+    <string name="light_theme_title">ብርሃን</string>
+    <string name="dark_theme_title">ጸልማት</string>
+    <string name="notification_action_1_title">ካልኣይ ስጉምቲ መጠወቒ</string>
+    <string name="notification_action_2_title">ሳልሳይ ስጉምቲ መጠወቒ</string>
+    <string name="notification_action_4_title">ሓሙሻይ ስጉምቲ መጠወቒ</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index b1ecacfc3..056434a4d 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -2,7 +2,7 @@
 <resources>
     <string name="main_bg_subtitle">Başlamak için büyütece dokunun.</string>
     <string name="upload_date_text">%1$s tarihinde yayınlandı</string>
-    <string name="no_player_found">Akış oynatıcısı bulunamadı. VLC kurulsun mu\?</string>
+    <string name="no_player_found">Video oynatıcısı bulunamadı. VLC kurulsun mu?</string>
     <string name="install">Kur</string>
     <string name="cancel">İptal</string>
     <string name="open_in_browser">Tarayıcıda aç</string>
@@ -25,7 +25,7 @@
     <string name="kore_not_found">Eksik Kore uygulaması yüklensin mi\?</string>
     <string name="show_play_with_kodi_title">\"Kodi ile oynat\" seçeneğini göster</string>
     <string name="show_play_with_kodi_summary">Kodi ortam merkezi üzerinden video oynatmak için bir seçenek göster</string>
-    <string name="default_audio_format_title">Öntanımlı ses biçimi</string>
+    <string name="default_audio_format_title">Varsayılan ses biçimi</string>
     <string name="theme_title">Tema</string>
     <string name="dark_theme_title">Koyu</string>
     <string name="light_theme_title">Açık</string>
@@ -77,11 +77,11 @@
     <string name="msg_copied">Panoya kopyalandı</string>
     <string name="no_available_dir">Lütfen daha sonra ayarlardan uygun indirme klasörü belirleyin</string>
     <string name="could_not_setup_download_menu">İndirme menüsü ayarlanamadı</string>
-    <string name="open_in_popup_mode">Açılır pencere kipinde aç</string>
-    <string name="default_popup_resolution_title">Öntanımlı açılır pencere çözünürlüğü</string>
+    <string name="open_in_popup_mode">Açılır pencere modunda aç</string>
+    <string name="default_popup_resolution_title">Varsayılan açılır pencere çözünürlüğü</string>
     <string name="show_higher_resolutions_title">Yüksek çözünürlükleri göster</string>
     <string name="show_higher_resolutions_summary">Yalnızca bazı aygıtlar 2K/4K videoları oynatabilir</string>
-    <string name="default_video_format_title">Öntanımlı video biçimi</string>
+    <string name="default_video_format_title">Varsayılan video biçimi</string>
     <string name="black_theme_title">Siyah</string>
     <string name="popup_playing_toast">Açılır pencere kipinde oynatılıyor</string>
     <string name="all">Tümü</string>
@@ -190,7 +190,7 @@
     <string name="switch_to_main">Ana Görünüme Geç</string>
     <string name="drawer_open">Çekmeceyi Aç</string>
     <string name="drawer_close">Çekmeceyi Kapat</string>
-    <string name="no_player_found_toast">Akış oynatıcı bulunamadı (Oynatmak için VLC kurabilirsiniz).</string>
+    <string name="no_player_found_toast">Video oynatıcı bulunamadı (Oynatmak için VLC kurabilirsiniz).</string>
     <string name="always">Her Zaman</string>
     <string name="just_once">Yalnızca Bir Kez</string>
     <string name="external_player_unsupported_link_type">Dış oynatıcılar bu tür bağlantıları desteklemez</string>
@@ -243,7 +243,7 @@
     <string name="enable_disposed_exceptions_summary">Parçanın dışında veya atımdan sonraki etkinlik yaşam döngüsündeki teslim edilemeyen Rx beklentilerinin bildirimini zorla</string>
     <string name="use_inexact_seek_title">Hızlı isabetsiz konumlama kullan</string>
     <string name="use_inexact_seek_summary">İsabetsiz konumlama, oynatıcının daha düşük hassasiyetle daha hızlı konumlamasını sağlar. 5, 15 veya 25 saniyeye konumlama bununla birlikte çalışmamaktadır</string>
-    <string name="auto_queue_title">Sonraki akışı kuyruğa kendiliğinden ekle</string>
+    <string name="auto_queue_title">Sonraki videoyu kuyruğa otomatik ekle</string>
     <string name="auto_queue_summary">İlgili bir akış ekleyerek sonlanacak (yinelemeyen) oynatma sırasını sürdür</string>
     <string name="file">Dosya</string>
     <string name="invalid_directory">Böylesi klasör yok</string>
@@ -280,7 +280,7 @@
 \n
 \nSürdürmek istiyor musunuz\?</string>
     <string name="thumbnail_cache_wipe_complete_notice">Resim önbelleği temizlendi</string>
-    <string name="metadata_cache_wipe_title">Önbelleğe alınmış üstverileri temizle</string>
+    <string name="metadata_cache_wipe_title">Önbelleğe alınmış meta verileri silin</string>
     <string name="metadata_cache_wipe_summary">Önbelleğe alınmış tüm web sayfası verilerini kaldır</string>
     <string name="metadata_cache_wipe_complete_notice">Üstveri önbelleği temizlendi</string>
     <string name="playback_speed_control">Oynatım Hızı Denetimleri</string>
@@ -423,7 +423,7 @@
         <item quantity="other">%s dinleyici</item>
     </plurals>
     <string name="localization_changes_requires_app_restart">Uygulama yeniden başlatıldıktan sonra dil değişecektir</string>
-    <string name="seek_duration_title">Hızlı ileri/geri konumlama süresi</string>
+    <string name="seek_duration_title">Hızlı ileri/geri atlama süresi</string>
     <string name="peertube_instance_url_title">PeerTube örnekleri</string>
     <string name="peertube_instance_url_summary">Favori PeerTube örneklerinizi seçin</string>
     <string name="peertube_instance_url_help">%s adresinde beğendiğiniz örnekleri bulun</string>
@@ -475,7 +475,7 @@
     <string name="feed_group_dialog_empty_selection">Abonelik seçilmedi</string>
     <plurals name="feed_group_dialog_selection_count">
         <item quantity="one">%d öğe seçildi</item>
-        <item quantity="other">"%d öğe  seçildi"</item>
+        <item quantity="other">%d öğe seçildi</item>
     </plurals>
     <string name="feed_group_dialog_empty_name">Boş grup adı</string>
     <string name="feed_group_dialog_delete_message">Bu grubu silmek istiyor musunuz\?</string>
@@ -534,16 +534,16 @@
     <string name="autoplay_summary">Oynatmayı kendiliğinden başlat — %s</string>
     <string name="title_activity_play_queue">Oynatma kuyruğu</string>
     <string name="unsupported_url_dialog_message">URL tanınamadı. Başka bir uygulamayla açılsın mı\?</string>
-    <string name="auto_queue_toggle">Kendiliğinden kuyruğa alış</string>
+    <string name="auto_queue_toggle">Otomatik kuyruğa alış</string>
     <string name="clear_queue_confirmation_description">Etkin oynatıcının kuyruğu değiştirilecek</string>
     <string name="clear_queue_confirmation_summary">Bir oynatıcıdan diğerine geçmek kuyruğunuzu değiştirebilir</string>
-    <string name="clear_queue_confirmation_title">Bir kuyruğu temizlemeden önce onay iste</string>
+    <string name="clear_queue_confirmation_title">Bir sırayı temizlemeden önce onay iste</string>
     <string name="notification_action_nothing">Hiçbir şey</string>
     <string name="notification_action_buffering">Ara belleğe alınıyor</string>
     <string name="notification_action_shuffle">Karıştır</string>
-    <string name="notification_action_repeat">Tekrarla</string>
+    <string name="notification_action_repeat">Yinele</string>
     <string name="notification_actions_at_most_three">Bildirim sekmesinde gösterilecek en fazla üç eylem seçebilirsiniz!</string>
-    <string name="notification_actions_summary">Aşağıdaki bildirim eyleminlerini üzerine dokunarak düzenleyin. Sağdaki onay kutularını kullanarak sıkışık bildirimde gösterilecek üç eylemi seçin</string>
+    <string name="notification_actions_summary">Aşağıdaki bildirim eyleminlerini üzerine dokunarak düzenleyin. Sağdaki onay kutularını kullanarak üst bildirimde gösterilecek üç eylemi seçin.</string>
     <string name="notification_action_4_title">Beşinci eylem düğmesi</string>
     <string name="notification_action_3_title">Dördüncü eylem düğmesi</string>
     <string name="notification_action_2_title">Üçüncü eylem düğmesi</string>
@@ -632,7 +632,7 @@
     <string name="high_quality_larger">Yüksek nitelik (geniş)</string>
     <string name="comments_are_disabled">Yorumlar devre dışı</string>
     <string name="detail_heart_img_view_description">Yaratıcısınca kalplendi</string>
-    <string name="mark_as_watched">İzlendi olarak imle</string>
+    <string name="mark_as_watched">İzlendi olarak işaretle</string>
     <string name="show_image_indicators_summary">Resimlerin üzerinde kaynaklarını gösteren Picasso renkli şeritler göster: ağ için kırmızı, disk için mavi ve bellek için yeşil</string>
     <string name="show_image_indicators_title">Resim göstergelerini göster</string>
     <string name="remote_search_suggestions">Uzak arama önerileri</string>
@@ -667,7 +667,7 @@
 \nLütfen Depolama Erişimi Çerçevesi uyumlu dosya yönetici kurun</string>
     <string name="error_report_channel_description">Hata raporları için bildirimler</string>
     <string name="show_crash_the_player_summary">Oynatıcı kullanırken çöktürme seçeneği gösterir</string>
-    <string name="crash_the_player">Oynatıcıyı çöktür</string>
+    <string name="crash_the_player">Oynatıcıyı çökert</string>
     <string name="detail_pinned_comment_view_description">Sabitlenmiş yorum</string>
     <string name="leak_canary_not_available">LeakCanary yok</string>
     <string name="progressive_load_interval_exoplayer_default">ExoPlayer öntanımlısı</string>
@@ -732,7 +732,7 @@
     <string name="volume">Ses düzeyi</string>
     <string name="none">Hiçbiri</string>
     <string name="left_gesture_control_summary">Parmağınızı ekranın sol yarısında kaydırırken ne yapılacağını seçin</string>
-    <string name="prefer_descriptive_audio_summary">Varsa, görsel engelli kişiler için betimlemeli ses parçasını seç</string>
+    <string name="prefer_descriptive_audio_summary">Varsa, görsel engelli kişiler için betimli ses parçasını seç</string>
     <string name="unknown_audio_track">Bilinmeyen</string>
     <string name="settings_category_exoplayer_title">ExoPlayer ayarları</string>
     <string name="settings_category_exoplayer_summary">Kimi ExoPlayer ayarlarını yönet. Bu değişiklikler oynatıcının yeniden başlatılmasını gerektirir</string>
@@ -741,15 +741,15 @@
     <string name="audio_track_type_descriptive">betimlemeli</string>
     <string name="audio_track_name">%1$s %2$s</string>
     <string name="select_audio_track_external_players">Dış oynatıcılar için ses parçası seç</string>
-    <string name="prefer_original_audio_summary">Dil ne olursa olsun özgün ses parçasını seç</string>
+    <string name="prefer_original_audio_summary">Dilden bağımsız olarak orijinal ses parçasını seçin</string>
     <string name="play_queue_audio_track">Ses: %s</string>
-    <string name="prefer_original_audio_title">Özgün sesi yeğle</string>
-    <string name="prefer_descriptive_audio_title">Betimlemeli sesi yeğle</string>
+    <string name="prefer_original_audio_title">Orijinal sesi tercih edin</string>
+    <string name="prefer_descriptive_audio_title">Açıklayıcı sesi tercih edin</string>
     <string name="audio_track">Ses parçası</string>
     <string name="audio_track_present_in_video">Bu akışta halihazırda bir ses parçası bulunmalıdır</string>
     <string name="no_streams">Akış yok</string>
     <string name="no_live_streams">Canlı akış yok</string>
-    <string name="right_gesture_control_title">Sağ kaydırma eylemi</string>
+    <string name="right_gesture_control_title">Sağ hareket işlemi</string>
     <string name="main_tabs_position_summary">Ana sekme seçiciyi alta taşı</string>
     <string name="main_tabs_position_title">Ana sekme konumu</string>
     <string name="disable_media_tunneling_automatic_info">Ortam tünelleme öntanımlı olarak devre dışı bırakılmıştır çünkü aygıt modelinizin desteği bilinmemektedir.</string>
@@ -759,7 +759,7 @@
 \n
 \nBu ayarı etkinleştirmek, geçerli video oynatıcısını değiştirirken veya tam ekrana geçerkenki oynatma hatalarını önleyebilir</string>
     <string name="progressive_load_interval_summary">Progresif içeriklerdeki yükleme aralığı boyutunu değiştir (şu anda %s). Daha düşük değer ilk yüklemeyi hızlandırabilir</string>
-    <string name="left_gesture_control_title">Sol kaydırma eylemi</string>
+    <string name="left_gesture_control_title">Sol hareket işlemi</string>
     <string name="right_gesture_control_summary">Parmağınızı ekranın sağ yarısında kaydırırken ne yapılacağını seçin</string>
     <string name="use_exoplayer_decoder_fallback_title">ExoPlayer\'ın çözücü yedek özelliğini kullan</string>
     <string name="image_quality_title">Resim niteliği</string>
@@ -806,4 +806,11 @@
     <string name="channel_tab_channels">Kanallar</string>
     <string name="previous_stream">Önceki akış</string>
     <string name="channel_tab_livestreams">Canlı</string>
+    <string name="show_more">Daha fazla göster</string>
+    <string name="show_less">Daha az göster</string>
+    <string name="notification_actions_summary_android13">Aşağıdaki her bir bildirim eylemini üzerine dokunarak düzenleyin. İlk üç eylem (oynat/duraklat, önceki ve sonraki) sistem tarafından ayarlanır ve özelleştirilemez.</string>
+    <plurals name="replies">
+        <item quantity="one">%s yanıt</item>
+        <item quantity="other">%s yanıt</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 16212c933..76813a04f 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -605,7 +605,7 @@
     <string name="notification_colorize_summary">Кольорувати повідомлення основним кольором мініатюри. Підтримується не всіма пристроями</string>
     <string name="notification_colorize_title">Кольорове сповіщення</string>
     <string name="notification_actions_at_most_three">У компактному сповіщенні є не більше трьох дій!</string>
-    <string name="notification_actions_summary">Дії можна змінити, натиснувши на них. Позначте не більше трьох для показу в компактному сповіщенні</string>
+    <string name="notification_actions_summary">Відредагуйте кожну дію сповіщення, натиснувши на неї. Виберіть до трьох з них, які будуть показані в компактному сповіщенні, за допомогою прапорців праворуч.</string>
     <string name="notification_scale_to_square_image_summary">Обрізати мініатюру відео показувану в сповіщенні з пропорцій 16: 9 до 1:1</string>
     <string name="disable_media_tunneling_summary">Вимкнення тунелювання медіаданих за наявності чорного екрана або гальмування під час відтворення відео.</string>
     <string name="disable_media_tunneling_title">Вимкнути тунелювання медіа</string>
@@ -657,7 +657,7 @@
         <item quantity="other">Видалено %1$s завантажень</item>
     </plurals>
     <plurals name="download_finished_notification">
-        <item quantity="one">Завантаження завершено</item>
+        <item quantity="one">%s завантаження завершено</item>
         <item quantity="few">%s завантаження завершено</item>
         <item quantity="many">%s завантажень завершено</item>
         <item quantity="other">%s завантажень завершено</item>
@@ -823,4 +823,13 @@
     <string name="share_playlist">Поділитися добіркою</string>
     <string name="share_playlist_with_titles_message">Поділитися добіркою з подробицями, такими як назва добірки та назви відео, або просто списком URL-адрес відео</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="show_more">Показати більше</string>
+    <string name="notification_actions_summary_android13">Відредагуйте кожну дію сповіщення, натиснувши на неї. Перші три дії (відтворення/пауза, попередній і наступний) встановлюються системою і не можуть бути змінені.</string>
+    <plurals name="replies">
+        <item quantity="one">%s відповідь</item>
+        <item quantity="few">%s відповіді</item>
+        <item quantity="many">%s відповідей</item>
+        <item quantity="other">%s відповідей</item>
+    </plurals>
+    <string name="show_less">Показати менше</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index f6719c1ed..bac361395 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -533,7 +533,7 @@
     <string name="notification_action_shuffle">Ngẫu nhiên</string>
     <string name="notification_action_repeat">Lặp lại</string>
     <string name="notification_actions_at_most_three">Bạn có thể chọn tối đa ba hành động để hiện trong thông báo thu gọn!</string>
-    <string name="notification_actions_summary">Chỉnh sửa mỗi hành động trong thông báo ở dưới bằng cách nhấn vào nó. Chọn tối đa ba hành động để hiện trong thông báo thu gọn bằng cách sử dụng các ô đánh dấu bên phải</string>
+    <string name="notification_actions_summary">Chỉnh sửa từng hành động thông báo bên dưới bằng cách nhấn vào nó. Chọn tối đa ba trong số chúng để hiển thị trong thông báo thu gọn bằng cách sử dụng các hộp kiểm ở bên phải.</string>
     <string name="notification_action_4_title">Nút hành động thứ năm</string>
     <string name="notification_action_3_title">Nút hành động thứ tư</string>
     <string name="notification_action_2_title">Nút hành động thứ ba</string>
@@ -551,7 +551,7 @@
     <string name="clear_cookie_summary">Xoá Cookie mà NewPipe lưu trữ sau khi bạn hoàn thành nó</string>
     <string name="recaptcha_cookies_cleared">Cookie reCAPTCHA đã được xóa</string>
     <string name="clear_cookie_title">Dọn dẹp Cookie của reCAPCHA</string>
-    <string name="youtube_restricted_mode_enabled_summary">YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung không phụ hợp</string>
+    <string name="youtube_restricted_mode_enabled_summary">YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung có khả năng dành cho người trưởng thành</string>
     <string name="notification_colorize_summary">Yêu cầu Android tùy chỉnh màu của thông báo theo màu chính của ảnh thu nhỏ (lưu ý rằng việc này không khả dụng trên tất cả thiết bị)</string>
     <string name="notification_colorize_title">Tô màu thông báo</string>
     <string name="no_app_to_open_intent">Thiết bị của bạn không có ứng dụng để mở tệp này</string>
@@ -793,4 +793,10 @@
     <string name="image_quality_summary">Chọn chất lượng hình ảnh và chọn có tải chất lượng ảnh hay không, để giảm mức sử dụng dữ liệu và bộ nhớ. Thay đổi xoá cache ảnh cho cả trong bộ nhớ lẫn ổ cứng - %s</string>
     <string name="channel_tab_tracks">Track</string>
     <string name="channel_tab_livestreams">Trực tiếp</string>
+    <string name="notification_actions_summary_android13">Chỉnh sửa từng hành động thông báo bên dưới bằng cách nhấn vào nó. Ba hành động đầu tiên (phát/tạm dừng, trước đó và tiếp theo) do hệ thống thiết lập và không thể tùy chỉnh.</string>
+    <string name="show_less">Hiện ít hơn</string>
+    <string name="show_more">Hiện nhiều hơn</string>
+    <plurals name="replies">
+        <item quantity="other">%s hồi đáp</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 47752a150..f1d3fbf61 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -534,7 +534,7 @@
     <string name="notification_action_shuffle">随机播放</string>
     <string name="notification_action_repeat">单曲循环</string>
     <string name="notification_actions_at_most_three">最多可以选择三个操作显示在紧凑通知中!</string>
-    <string name="notification_actions_summary">点击编辑下面的每一个通知操作。使用右方的复选框选择在紧凑通知中显示的动作,最多可以选择三个</string>
+    <string name="notification_actions_summary">轻按下面的每一个通知操作进行编辑。使用右方的复选框选择在紧凑通知中显示的操作,最多可以选择三个。</string>
     <string name="notification_action_4_title">第五操作按钮</string>
     <string name="notification_action_3_title">第四操作按钮</string>
     <string name="notification_action_2_title">第三操作按钮</string>
@@ -793,4 +793,10 @@
     <string name="share_playlist">分享播放列表</string>
     <string name="share_playlist_with_titles_message">分享详细的播放列表(带名称和视频标题等信息)或只分享视频网址列表</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
+    <plurals name="replies">
+        <item quantity="other">%s 条回复</item>
+    </plurals>
+    <string name="show_more">显示较多</string>
+    <string name="show_less">显示较少</string>
+    <string name="notification_actions_summary_android13">轻按下方的每个通知操作进行编辑。头三个动作(播放/暂停、上一个和下一个)是系统设置的,不能自定义。</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 3afaf86de..e9eb5bf12 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -44,7 +44,7 @@
     <string name="main_bg_subtitle">撳放大鏡開始。</string>
     <string name="content">內容</string>
     <string name="show_age_restricted_content_title">顯示年齡設限嘅內容</string>
-    <string name="duration_live">實況</string>
+    <string name="duration_live">直播</string>
     <string name="general_error">問題</string>
     <string name="could_not_load_thumbnails">載入唔晒全部縮圖</string>
     <string name="parsing_error">解析唔到網站</string>
@@ -155,7 +155,7 @@
     <string name="open_with">開啟方式係</string>
     <string name="search_showing_result_for">搜尋到嘅嘢係:%s</string>
     <string name="notification_action_4_title">第五粒動作掣</string>
-    <string name="notification_actions_summary">撳下面嘅掣去更改對應嘅通知動作。用右手邊嘅格仔剔選最多三個,擺喺精簡通知度</string>
+    <string name="notification_actions_summary">撳下面嘅掣去更改對應嘅通知動作。用右手邊嘅格仔剔選最多三個,擺落去精簡通知度。</string>
     <string name="notification_actions_at_most_three">精簡通知最多淨係擺到三個動作!</string>
     <string name="notification_action_repeat">循環播放</string>
     <string name="show_comments_title">顯示留言</string>
@@ -516,7 +516,7 @@
     <string name="metadata_host">主機</string>
     <string name="show_hold_to_append_summary">喺影片詳情撳一下「幕後播」或「浮面播」個掣嘅時候顯示提示</string>
     <string name="title_activity_history">紀錄</string>
-    <string name="action_history">歷史紀錄</string>
+    <string name="action_history">紀錄</string>
     <string name="enqueue_next_stream">攝咗做下個播</string>
     <string name="enable_leak_canary_summary">記憶體洩露監測喺傾卸堆疊嘢嘅時候,或者會導致個 app 冇反應</string>
     <string name="drawer_close">閂埋選單</string>
@@ -721,7 +721,7 @@
     <string name="right_gesture_control_summary">揀選右手邊播放器螢幕嘅手勢</string>
     <string name="right_gesture_control_title">右手邊手勢動作</string>
     <string name="brightness">調整光暗度</string>
-    <string name="volume">調整大細聲</string>
+    <string name="volume">音量</string>
     <string name="none">乜都唔使做</string>
     <string name="prefer_original_audio_title">優先揀原聲</string>
     <string name="prefer_descriptive_audio_title">優先揀旁述</string>
@@ -793,4 +793,10 @@
     <string name="channel_tab_channels">頻道</string>
     <string name="previous_stream">上一個串流</string>
     <string name="channel_tab_livestreams">直播</string>
+    <plurals name="replies">
+        <item quantity="other">%s 個回覆</item>
+    </plurals>
+    <string name="show_less">摺埋</string>
+    <string name="show_more">拉開</string>
+    <string name="notification_actions_summary_android13">撳下面嘅掣去更改對應嘅通知動作。頭三個動作 (播放/暫停、上一個、下一個) 系統預設咗,冇得揀。</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 8e613161a..382a95479 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -534,7 +534,7 @@
     <string name="notification_action_shuffle">隨機播放</string>
     <string name="notification_action_repeat">循環播放</string>
     <string name="notification_actions_at_most_three">您可以選取最多三個動作來顯示在簡潔通知中!</string>
-    <string name="notification_actions_summary">透過輕觸下方的通知來編輯它。使用右側的勾選框,最多可以選取三個在簡潔通知中顯示</string>
+    <string name="notification_actions_summary">透過輕觸下方的通知來編輯它。使用右側的勾選框,最多可以選取三個在簡潔通知中顯示。</string>
     <string name="notification_action_4_title">第五動作按鈕</string>
     <string name="notification_action_3_title">第四動作按鈕</string>
     <string name="notification_action_2_title">第三動作按鈕</string>
@@ -793,4 +793,10 @@
     <string name="share_playlist">分享播放清單</string>
     <string name="share_playlist_with_titles_message">分享包含播放清單名稱與影片標題等詳細資訊的播放清單,或是僅作為簡單的影片 URL 清單</string>
     <string name="video_details_list_item">- %1$s:%2$s</string>
+    <plurals name="replies">
+        <item quantity="other">%s 個回覆</item>
+    </plurals>
+    <string name="show_more">顯示更多</string>
+    <string name="show_less">顯示較少</string>
+    <string name="notification_actions_summary_android13">透過點擊下面的每個通知操作來編輯它。前三個動作(播放/暫停、上一個與下一個)由系統設定,無法自訂。</string>
 </resources>
\ No newline at end of file
diff --git a/fastlane/metadata/android/ar/changelogs/996.txt b/fastlane/metadata/android/ar/changelogs/996.txt
new file mode 100644
index 000000000..1dd4e3de5
--- /dev/null
+++ b/fastlane/metadata/android/ar/changelogs/996.txt
@@ -0,0 +1,2 @@
+تم إصلاح NullPointerException عند فتح قناة / مؤتمر في media.ccc.de.
+حاول احدهم كسر الهدية التي قدمناها لك، لكننا تخطينا الأمر.
diff --git a/fastlane/metadata/android/bg/changelogs/64.txt b/fastlane/metadata/android/bg/changelogs/64.txt
new file mode 100644
index 000000000..f5ece1568
--- /dev/null
+++ b/fastlane/metadata/android/bg/changelogs/64.txt
@@ -0,0 +1,6 @@
+### Подобрения
+-Добавена е възможността да се ограничи качеството на видеото ако се използва мобилен интернет. #1339 
+- Запаметява яркостта за сесията #1442 
+- Оптимизиране при изтегляне с по-слаби процесори #1431 
+### Поправени
+- Поправен срив при отваряне на изтегления #1441
diff --git a/fastlane/metadata/android/bn/changelogs/64.txt b/fastlane/metadata/android/bn/changelogs/64.txt
index d5e5901e5..e76c3130e 100644
--- a/fastlane/metadata/android/bn/changelogs/64.txt
+++ b/fastlane/metadata/android/bn/changelogs/64.txt
@@ -1,5 +1,5 @@
 ### অগ্রগতিসমূহ
-- মোবাইল ডাটা ব্যবহার করার সময় ভিডিও মানের সীমা নির্ধারণ করা যাবে।  #1339।
+- মোবাইল ডাটা ব্যবহার করার সময় ভিডিও মানের সীমা নির্ধারণ করা যাবে। #1339।
 - প্লেয়ারের উজ্জ্বলতা মনে রাখুন #1442।
 - দুর্বল সিপিইউর ডিভাইসে পারফরম্যান্স উন্নত করা হয়েছে #1431।
 - মিডিয়া সেশন এখন সঠিকভাবে কাজ করে #1433।
diff --git a/fastlane/metadata/android/cs/changelogs/996.txt b/fastlane/metadata/android/cs/changelogs/996.txt
new file mode 100644
index 000000000..a220330bc
--- /dev/null
+++ b/fastlane/metadata/android/cs/changelogs/996.txt
@@ -0,0 +1,2 @@
+Opravena chyba NullPointerException při otevírání kanálu / konference na media.ccc.de.
+Ježíšek nám málem doručil rozbitý dárek pro vás, ale opravili jsme to.
diff --git a/fastlane/metadata/android/de/changelogs/65.txt b/fastlane/metadata/android/de/changelogs/65.txt
index 3405af363..6815d432a 100644
--- a/fastlane/metadata/android/de/changelogs/65.txt
+++ b/fastlane/metadata/android/de/changelogs/65.txt
@@ -1,4 +1,5 @@
 Verbesserungen
+
 -Menüanimation deaktiviert #1486
 -Löschen von Downloads rückgängig machen #1472
 -Option zum Downloaden im Teilen-Menü #1498
@@ -6,5 +7,4 @@ Verbesserungen
 -Minimieren der Wiedergabe beim Beenden #1354
 -Datenbank Versions Aktualisierung und Datenbestand Wiederherstellung #1510
 -ExoPlayer 2.8.2 aktualisiert #1392
--Überarbeitung der Wiedergabegeschwindigkeitskontrolle um Änderungen besser zu unterstützen
--hinzufügen der Möglichkeit Stille zu überspringen(Hilfreich bei Audiobücher und einigen Musikarten) unterstützt dadurch eine nahtlose Wiedergabe (Nicht so toll bei Liedern, in denen es auch stille Passagen gibt.)
+-Überarbeitung der Wiedergabegeschwindigkeitskontrolle um Änderungen besser zu unterstützen.
diff --git a/fastlane/metadata/android/de/changelogs/66.txt b/fastlane/metadata/android/de/changelogs/66.txt
new file mode 100644
index 000000000..937bfd642
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/66.txt
@@ -0,0 +1,18 @@
+# Versionsänderung von v0.13.7
+
+### Behebungen
+- Probleme der Filterung von v.0.13.6 behoben
+
+# Versionsänderung von v0.13.6
+
+### Verbesserungen
+
+- Animation der Burgermenü-Icon entfernt #1486
+- Löschen von Downloads rückgängig machen #1472
+- Im "Teilen"-Tab die Download option hinzugefügt #1498
+- Option "Teilen" zum Long-Tap-Menü hinzugefügt #1454
+
+### Behebungen
+
+- Probleme mit dem kaputten Video Info layout behoben. #1440 / #1491
+- Probleme mit dem Verlauf wurden behoben. #1497.
diff --git a/fastlane/metadata/android/de/changelogs/68.txt b/fastlane/metadata/android/de/changelogs/68.txt
new file mode 100644
index 000000000..6cdbd55a0
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/68.txt
@@ -0,0 +1,19 @@
+# Änderungen von v0.14.1
+
+### Fixed
+- nicht entschlüsselt Video url #1659
+- Beschreibungs Link nicht extrahierbar #1657
+
+# Änderungen von v0.14.0
+
+### New
+- Neues Schubladendesign #1461
+- Neue anpassbare Titelseite #1461
+
+### Verbesserungen
+- Reworked Gesture Controls #1604
+- Neue Möglichkeit, den Popup-Player #1597 zu schließen
+
+### Fixed
+- Fehler beheben, wenn die Anzahl der Abonnements nicht verfügbar ist. Schließt #1649.
+- Zeigen Sie "Abonnentenzählung nicht verfügbar" in diesen Fällen.
diff --git a/fastlane/metadata/android/de/changelogs/69.txt b/fastlane/metadata/android/de/changelogs/69.txt
new file mode 100644
index 000000000..8802508a3
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/69.txt
@@ -0,0 +1,14 @@
+### New
+- Long-tap Löschen & Teilen in Abonnements #1516
+- Tablet UI & Rasterlistenlayout #1617
+
+### Verbesserungen
+- Speichern und Nachladen des zuletzt verwendeten Seitenverhältnisses #1748
+- Separate Einstellungen für Lautstärke & Helligkeitsgesten #1644
+
+### Fixes
+- Anzahl der Abonnements
+- Foreground Service Erlaubnis für API 28+ Geräte #1830 hinzugefügt
+
+### Known Bugs
+- Wiedergabe kann nicht auf Android P gespeichert werden
diff --git a/fastlane/metadata/android/de/changelogs/70.txt b/fastlane/metadata/android/de/changelogs/70.txt
new file mode 100644
index 000000000..59a09b210
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/70.txt
@@ -0,0 +1,10 @@
+ACHTUNG: Diese Version ist wahrscheinlich ein Bugfest.
+
+### Verbesserungen
+* heruntergeladene Dateien können mit einem Klick #1879 geöffnet werden
+* Drop-Unterstützung für Android 4.1 - 4.3 #1884
+* Streams aus der aktuellen Warteschlange entfernt, indem sie nach rechts swipen #1915
+
+### Fixed
+* Crash mit Standard-Auflösung eingestellt auf beste und begrenzte mobile Datenauflösung #1835
+* Pop-up-Spieler-Absturz behoben #1874
diff --git a/fastlane/metadata/android/de/changelogs/71.txt b/fastlane/metadata/android/de/changelogs/71.txt
new file mode 100644
index 000000000..0132de3c3
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/71.txt
@@ -0,0 +1,8 @@
+### Verbesserungen
+* App-Update-Benachrichtigung für GitHub build hinzufügen (#1608 von @krtkush)
+* Verschiedene Verbesserungen des Downloaders (#1944 von @kapodamy):
+* Fügen Sie fehlende weiße Icons hinzu und verwenden Sie hardcored Weg, um die Icon Farben zu ändern
+* neue MPEG-4 muxer fixieren nicht-synchrone Video- und Audiostreams (#2039)
+
+### Fixed
+* YouTube Live-Streams spielen nach kurzer Zeit (#1996 von @yausername)
diff --git a/fastlane/metadata/android/de/changelogs/740.txt b/fastlane/metadata/android/de/changelogs/740.txt
new file mode 100644
index 000000000..0d28a157a
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/740.txt
@@ -0,0 +1,12 @@
+<h4>Verbesserungen</h4>
+<ul>
+<li>Make Links in Kommentare klickbar, erhöhen Textgröße</li>
+<li>seek zum Klicken von Zeitstempel-Links in Kommentare</li>
+<li>Beliebte Registerkarte basierend auf kürzlich ausgewähltem Zustand anzeigen</li>
+<li>Add-Unterstützung für Invidious links</li>
+</ul>
+<h4>Fixed</h4>
+<ul>
+<li>fixed scroll w/kommentare und verwandten Streams deaktiviert</li>
+<li>fixiert CheckForNewAppVersionTask wird ausgeführt, wenn es sollten&#39;t</li>
+</ul>
diff --git a/fastlane/metadata/android/de/changelogs/750.txt b/fastlane/metadata/android/de/changelogs/750.txt
new file mode 100644
index 000000000..78a25282a
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/750.txt
@@ -0,0 +1,15 @@
+Neu
+Playback Lebenslauf #2288
+• Resume Streams, wo Sie letztes Mal aufgehört haben
+Downloader Verbesserungen #2149
+
+
+Verbessert
+• Gemaketten entfernen #2295
+• Handle (auto)Rotationsänderungen während des Aktivitätszyklus #2444
+
+Behoben
+• Fixed Downloads bei 99,9% #2440
+• Aktualisieren der Spielwarteschlange Metadaten #2453
+• [SoundCloud] Fester Absturz beim Laden von Wiedergabelisten TeamNewPipe/NewPipeExtractor#170
+• [YouTube] Feste Dauer kann nicht paresd TeamNewPipe/NewPipeExtractor#177
diff --git a/fastlane/metadata/android/de/changelogs/760.txt b/fastlane/metadata/android/de/changelogs/760.txt
new file mode 100644
index 000000000..f0eac002f
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/760.txt
@@ -0,0 +1,24 @@
+Veränderung in 0,17.1
+
+Neu
+• thailändische Lokalisierung
+
+
+Verbessert
+• Schalter für SAF / Legacy Datei Picker #2521 hinzufügen
+
+Behoben
+• Löschen Sie Löschtasten in Downloads Ansicht beim Umschalten von Apps #2487
+
+Veränderungen in 0,17,0
+
+Neu
+Playback Lebenslauf #2288
+• Resume Streams, wo Sie letztes Mal aufgehört haben
+Downloader Verbesserungen #2149
+
+Verbessert
+• Gemaketten entfernen #2295
+
+Behoben
+• Behoben ausgewählter Subtitle Track Name nicht angezeigt #2394
diff --git a/fastlane/metadata/android/de/changelogs/780.txt b/fastlane/metadata/android/de/changelogs/780.txt
new file mode 100644
index 000000000..4deac3709
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/780.txt
@@ -0,0 +1,11 @@
+Veränderungen in 0,17,3
+
+Verbessert
+• Hinzugefügte Option, um die Wiedergabezustände #2550 zu löschen
+• Versteckte Verzeichnisse in der Datei Picker #2591 anzeigen
+• Unterstützung von URLs von `invidio.us` Instanzen, die mit NewPipe #2488 geöffnet werden sollen
+• Unterstützung für `music.youtube.com` URLs TeamNewPipe/NewPipeExtractor#194
+
+Behoben
+• [YouTube] Behoben 'java.lang.IllegalArgumentException #192
+• [YouTube] Feste Live-Streams nicht arbeiten TeamNewPipe/NewPipeExtractor#195
diff --git a/fastlane/metadata/android/de/changelogs/790.txt b/fastlane/metadata/android/de/changelogs/790.txt
new file mode 100644
index 000000000..345cb37aa
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/790.txt
@@ -0,0 +1,13 @@
+Verbessert
+• Weitere Titel hinzufügen, um die Zugänglichkeit für Blinde zu verbessern #2655
+• Sprache des Download-Ordners konsistenter und weniger eindeutig #2637
+
+Behoben
+• Fixed Scrolling in Video Detail Fragment #2672
+• Entfernen Sie doppelte Suche klare Box-Animationen zu einem #2695
+• [SoundCloud] Client_id Extraktion fix #2745
+
+Entwicklung
+• Hinzufügen fehlender Abhängigkeiten von NewPipeExtractor in NewPipe #2535
+• Migration auf AndroidX #2685
+• Update auf ExoPlayer 2.10.6 #2697, #2736
diff --git a/fastlane/metadata/android/de/changelogs/800.txt b/fastlane/metadata/android/de/changelogs/800.txt
new file mode 100644
index 000000000..792b15461
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/800.txt
@@ -0,0 +1,11 @@
+Neu
+• PeerTube Unterstützung ohne P2P (#2201) (Beta)
+ Es könnte Probleme mit SSL Handshakes auf Android 4.4 & 7.1 geben, wenn auf bestimmte Instanzen zugegriffen wird.
+
+Verbessert
+• Neue Localization and Downloader von Extractor #2713
+• Schwarze Navigationsleiste für schwarzes Theme #2569
+
+Behoben
+• Fehler wurde behoben, der den Popup-Player nicht verschieben konnte, wenn ein anderer Finger beim Verschieben des Popup-Players #2772 platziert wurde
+• [SoundCloud] Audio-Stream-Extraktion
diff --git a/fastlane/metadata/android/de/changelogs/810.txt b/fastlane/metadata/android/de/changelogs/810.txt
new file mode 100644
index 000000000..9b57ce03c
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/810.txt
@@ -0,0 +1,11 @@
+Neu
+• Video Vorschau auf den Sperrbildschirm beim Spielen im Hintergrund anzeigen
+
+Verbessert
+• Fügen Sie lokale Wiedergabeliste hinzu, um zu löschen, wenn lange Drücken auf Hintergrund / Popup-Taste
+
+Behoben
+• Mehrere Probleme behoben, wenn ein Video zu NewPipe geteilt und seine Streams direkt herunterladen
+• [YouTube] Festes Einschalten Null verursacht NPE
+• [YouTube] Behobene Betrachtungskommentare beim Öffnen eines invidio.us url
+• [SoundCloud] Update von client_id
diff --git a/fastlane/metadata/android/de/changelogs/840.txt b/fastlane/metadata/android/de/changelogs/840.txt
new file mode 100644
index 000000000..73e9904ec
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/840.txt
@@ -0,0 +1,10 @@
+Neu
+• Sprachauswahl hinzugefügt, um die App-Sprache zu ändern
+• Hinzugefügt senden zu Kodi Taste zu Spieler zusammenklappbare Menü
+
+Verbessert
+• Entfernen von dot-menu für Schublade und verstecken Geschichte Taste, wenn die Uhr Geschichte nicht in den Einstellungen aktiviert ist
+
+Behoben
+• Fix initialer Helligkeitswert für die Geste
+• [YouTube] Timestamps in der Beschreibung sind wieder anklickbar
diff --git a/fastlane/metadata/android/de/changelogs/930.txt b/fastlane/metadata/android/de/changelogs/930.txt
new file mode 100644
index 000000000..e2ddba83f
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/930.txt
@@ -0,0 +1,12 @@
+Neu
+• Suche auf YouTube Musik
+• Basic Android TV-Unterstützung
+
+Verbessert
+• Hinzugefügt die Fähigkeit, alle beobachteten Videos von einer lokalen Wiedergabeliste zu entfernen
+• Nachricht anzeigen, wenn der Inhalt noch nicht unterstützt wird, anstatt zu stürzen
+
+Behoben
+• Feste altersbeschränkte Inhaltseinstellung nicht funktionieren
+• Behoben bestimmte Arten von reCAPTCHAs
+• Feste Sichtbarkeit der Gruppen-Sort-Taste im Abonnement-Bereich
diff --git a/fastlane/metadata/android/de/changelogs/940.txt b/fastlane/metadata/android/de/changelogs/940.txt
index 3cbf73516..e96340b16 100644
--- a/fastlane/metadata/android/de/changelogs/940.txt
+++ b/fastlane/metadata/android/de/changelogs/940.txt
@@ -10,4 +10,3 @@ Verbessert
 
 Behoben
 - Einfrieren des Download-Dialogs behoben
-...
diff --git a/fastlane/metadata/android/de/changelogs/951.txt b/fastlane/metadata/android/de/changelogs/951.txt
index 7c6d93982..790d6329d 100644
--- a/fastlane/metadata/android/de/changelogs/951.txt
+++ b/fastlane/metadata/android/de/changelogs/951.txt
@@ -8,4 +8,3 @@ Neu
 Verbessert
 - Anwendungsmetadaten nicht mehr in gemuxte Dateien schreiben
 - Fehlerhafte Streams nicht aus der Warteschlange entfernen
-...
diff --git a/fastlane/metadata/android/de/changelogs/990.txt b/fastlane/metadata/android/de/changelogs/990.txt
index a629c8654..6e56a8e70 100644
--- a/fastlane/metadata/android/de/changelogs/990.txt
+++ b/fastlane/metadata/android/de/changelogs/990.txt
@@ -8,8 +8,6 @@ Neu:
 Verbessert:
 • Player-Code in kleine Komponenten refaktorisiert: weniger RAM-Verbrauch, weniger Bugs
 • Skalierungsmodus für Miniaturansicht
-…
 
 Behoben:
 • Verschiedene Probleme mit Player-Benachrichtigung: veraltete/fehlende Medieninfos, verzerrte Miniaturansicht
-…
diff --git a/fastlane/metadata/android/de/changelogs/995.txt b/fastlane/metadata/android/de/changelogs/995.txt
index 9c9ed1147..0c026e50f 100644
--- a/fastlane/metadata/android/de/changelogs/995.txt
+++ b/fastlane/metadata/android/de/changelogs/995.txt
@@ -11,6 +11,4 @@ Verbessert
 Behoben
 • [YouTube] Erhalten der Like-Anzahl
 • Nicht reagierende Popups und Abstürze des Players
-• Auswahl der falschen Sprachen in der Sprachauswahl
-• Audio-Fokus des Players respektiert keine Stummschaltung
-…
+• Hinzufügen von Wiedergabelistenelementen funktionierte gelegentlich nicht
diff --git a/fastlane/metadata/android/de/changelogs/996.txt b/fastlane/metadata/android/de/changelogs/996.txt
new file mode 100644
index 000000000..bcdea8f4f
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/996.txt
@@ -0,0 +1,2 @@
+Eine NullPointerException beim Öffnen eines Kanals / einer Konferenz in media.ccc.de behoben.
+Der Grinch hat versucht, unser Weihnachtsgeschenk für Sie zu zerstören, aber wir haben es behoben.
diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt
index a476e427e..ef2ac9577 100644
--- a/fastlane/metadata/android/de/full_description.txt
+++ b/fastlane/metadata/android/de/full_description.txt
@@ -1 +1 @@
-NewPipe verwendet keine Bibliotheken des Google-Frameworks oder der YouTube-API. Es analysiert die Website, um die benötigten Informationen zu erlangen. Aus diesem Grund kann die App ohne die Google Services verwendet werden. Ebenso wird kein YouTube-Konto für NewPipe benötigt und es ist FLOSS (Freie Software / Open-Source-Software).
+NewPipe verwendet keine Bibliotheken des Google-Frameworks oder der YouTube-API. Es analysiert die Website, um die benötigten Informationen zu erlangen. Aus diesem Grund kann die App ohne die Google Services verwendet werden. Ebenso wird kein YouTube-Konto für NewPipe benötigt und es ist FOSS (Freie Software / Open-Source-Software).
diff --git a/fastlane/metadata/android/de/short_description.txt b/fastlane/metadata/android/de/short_description.txt
index 138e55523..ec47872eb 100644
--- a/fastlane/metadata/android/de/short_description.txt
+++ b/fastlane/metadata/android/de/short_description.txt
@@ -1 +1 @@
-Eine freie, leichtgewichtige YouTube-App für Android.
+Eine freie, leichte YouTube-App für Android.
diff --git a/fastlane/metadata/android/el/changelogs/65.txt b/fastlane/metadata/android/el/changelogs/65.txt
new file mode 100644
index 000000000..0417512c7
--- /dev/null
+++ b/fastlane/metadata/android/el/changelogs/65.txt
@@ -0,0 +1,26 @@
+### Βελτιώσεις
+
+- Απενεργοποίηση της κίνησης του εικονιδίου του burgermenu #1486 
+- Αναίρεση διαγραφής λήψεων #1472 
+- Επιλογή λήψης στο μενού κοινής χρήσης #1498
+- Προστέθηκε επιλογή κοινής χρήσης στο μενού παρατεταμένου πατήματος #1454 
+- Ελαχιστοποίηση του κύριου προγράμματος αναπαραγωγής κατά την έξοδο #1354 
+- Ενημέρωση της έκδοσης της βιβλιοθήκης και διόρθωση αντιγράφων ασφαλείας της βάσης δεδομένων #1510 
+- Ενημέρωση ExoPlayer 2.8.2 #1392
+  - Ανασχεδιάστηκε ο διάλογος ελέγχου της ταχύτητας αναπαραγωγής ώστε να υποστηρίζει διαφορετικά μεγέθη βημάτων για ταχύτερη αλλαγή ταχύτητας.
+  - Προστέθηκε μια εναλλαγή για γρήγορη μετακίνηση προς τα εμπρός κατά τη διάρκεια σιωπής στον έλεγχο ταχύτητας αναπαραγωγής. Αυτό θα πρέπει να είναι χρήσιμο για τα ακουστικά βιβλία και ορισμένα είδη μουσικής και μπορεί να φέρει μια πραγματικά απρόσκοπτη εμπειρία (και μπορεί να σπάσει ένα τραγούδι με πολλές σιωπές =\\).  
+  - Αναδιαμόρφωση της ανάλυσης πηγής πολυμέσων ώστε να επιτρέπεται η μετάδοση μεταδεδομένων μαζί με τα πολυμέσα εσωτερικά στον αναπαραγωγέα, αντί να γίνεται χειροκίνητα. Τώρα έχουμε μια ενιαία πηγή μεταδεδομένων και είναι άμεσα διαθέσιμα κατά την έναρξη της αναπαραγωγής.
+  - Διορθώθηκε η μη ενημέρωση των απομακρυσμένων μεταδεδομένων της λίστας αναπαραγωγής όταν είναι διαθέσιμα νέα μεταδεδομένα κατά το άνοιγμα του τμήματος της λίστας αναπαραγωγής.
+  - Διάφορες διορθώσεις στο UI: #1383, τα στοιχεία ελέγχου ειδοποίησης αναπαραγωγής στο παρασκήνιο είναι τώρα πάντα λευκά, είναι ευκολότερο να κλείσετε το αναδυόμενο πρόγραμμα αναπαραγωγής μέσω εκτίναξης
+- Χρήση νέου εξαηωγέα με ανασχεδιασμένη αρχιτεκτονική για πολλαπλές υπηρεσίες
+
+### Διορθώσεις
+
+- Διόρθωση #1440 Προβληματική διάταξη πληροφοριών βίντεο #1491 
+- Διόρθωση ιστορικού προβολής #1497
+  - #1495, ενημέρωση των μεταδεδομένων (μικρογραφία, τίτλος και αριθμός βίντεο) μόλις ο χρήστης αποκτήσει πρόσβαση στη λίστα αναπαραγωγής. 
+  - #1475, με την καταχώριση μιας προβολής στη βάση δεδομένων όταν ο χρήστης ξεκινάει ένα βίντεο σε εξωτερική συσκευή αναπαραγωγής σε λεπτομερές θραύσμα.
+- Διόρθωση του χρονικού ορίου creen σε περίπτωση λειτουργίας αναδυόμενου παραθύρου. #1463 (Διορθώθηκε το #640)
+- Διόρθωση του κύριου προγράμματος αναπαραγωγής βίντεο #1509 
+  - #1412] Διορθώθηκε η λειτουργία επανάληψης που προκαλεί NPE του αναπαραγωγέα όταν λαμβάνεται νέα εντολή ενώ εργάζεται στο παρασκήνιο.
+  - Διορθώθηκε κατά την ελαχιστοποίηση του αναπαραγωγέα σε αναδυόμενο παράθυρο, να μην τον καταστρέφει όταν δεν έχει χορηγηθεί άδεια αναδυόμενου παραθύρου.
diff --git a/fastlane/metadata/android/el/changelogs/963.txt b/fastlane/metadata/android/el/changelogs/963.txt
new file mode 100644
index 000000000..b6c30aa9d
--- /dev/null
+++ b/fastlane/metadata/android/el/changelogs/963.txt
@@ -0,0 +1 @@
+• [YouTube] Διόρθωση της συνέχισης ενός καναλιού
diff --git a/fastlane/metadata/android/el/changelogs/996.txt b/fastlane/metadata/android/el/changelogs/996.txt
new file mode 100644
index 000000000..58223cb1a
--- /dev/null
+++ b/fastlane/metadata/android/el/changelogs/996.txt
@@ -0,0 +1,2 @@
+Διορθώθηκε μια NullPointerException κατά το άνοιγμα ενός καναλιού/συνεδρίου στο media.ccc.de.
+Ο Grinch προσπάθησε να σπάσει το χριστουγεννιάτικο δώρο μας προς εσάς, αλλά το διορθώσαμε.
diff --git a/fastlane/metadata/android/es/changelogs/996.txt b/fastlane/metadata/android/es/changelogs/996.txt
new file mode 100644
index 000000000..e620b525f
--- /dev/null
+++ b/fastlane/metadata/android/es/changelogs/996.txt
@@ -0,0 +1,2 @@
+Corregida una NullPointerException al abrir un canal / conferencia en media.ccc.de.
+El Grinch trató de romper nuestro regalo de Navidad para usted, pero lo arreglamos.
diff --git a/fastlane/metadata/android/fr/changelogs/750.txt b/fastlane/metadata/android/fr/changelogs/750.txt
index 1276a514d..422529abf 100644
--- a/fastlane/metadata/android/fr/changelogs/750.txt
+++ b/fastlane/metadata/android/fr/changelogs/750.txt
@@ -2,7 +2,7 @@ Nouveau
 Reprise de lecture #2288
 • Reprise des flux où ils s'étaient arrêtés
 Améliorations du téléchargeur #2149
-• Utilisation du Storage Access Framework pour stocker sur  cartes SD
+• Utilisation du Storage Access Framework pour stocker sur cartes SD
 • Nouveau multiplexeur mp4
 • Peut changer le dossier de téléchargement
 • Respect des réseaux tarifés
@@ -10,12 +10,12 @@ Améliorations du téléchargeur #2149
 
 Améliorations
 • Suppression des chaînes gema #2295
-• Gestion des changements de rotation  #2444
+• Gestion des changements de rotation #2444
 • Uniformisation des menus longue-pression #2368
 
 Corrections
 • Nom de la piste de sous-titres sélectionnée qui ne s'affiche pas #2394
-• Ne plante pas quand la vérification de la mise à jour de l'application échoue  #2423
+• Ne plante pas quand la vérification de la mise à jour de l'application échoue #2423
 • Téléchargements bloqués à 99,9 % #2440
 • Mise à jour des métadonnées de la file de lecture #2453
 • [SoundCloud] Ne plante pas lors du chargement des playlists TeamNewPipe/NewPipeExtractor#170
diff --git a/fastlane/metadata/android/hi/changelogs/996.txt b/fastlane/metadata/android/hi/changelogs/996.txt
new file mode 100644
index 000000000..0a9369d87
--- /dev/null
+++ b/fastlane/metadata/android/hi/changelogs/996.txt
@@ -0,0 +1,2 @@
+Media.ccc.de में चैनल/कॉन्फ़्रेंस खोलते समय NullPointerException को ठीक किया गया।
+ग्रिंच ने आपको हमारा क्रिसमस उपहार तोड़ने की कोशिश की, लेकिन हमने इसे ठीक कर दिया।
diff --git a/fastlane/metadata/android/hr/full_description.txt b/fastlane/metadata/android/hr/full_description.txt
index 14826925e..1e3977d33 100644
--- a/fastlane/metadata/android/hr/full_description.txt
+++ b/fastlane/metadata/android/hr/full_description.txt
@@ -1 +1 @@
-NewPipe ne koristi nijednu Googleovu biblioteku niti YouTube API, već samo analizira web-stranicu kako bi pribavio potrebne podatke. Stoga se ovaj program može koristiti na uređajima bez da su Googleove usluge instalirane. Također, za korištenje programa NewPipe nije potreban YouTube račun. NewPipe je slobodan program otvorenog koda.
+NewPipe ne koristi nijednu Googleovu biblioteku niti YouTube API, već samo analizira web-stranicu kako bi pribavio potrebne podatke. Stoga se ova aplikacija može koristiti na uređajima bez instaliranih Google usluga. Također, NewPipe se može koristiti bez YouTube računa. NewPipe je slobodna aplikacija otvorenog koda.
diff --git a/fastlane/metadata/android/id/changelogs/66.txt b/fastlane/metadata/android/id/changelogs/66.txt
index 62c8fd3e7..adbbff49c 100644
--- a/fastlane/metadata/android/id/changelogs/66.txt
+++ b/fastlane/metadata/android/id/changelogs/66.txt
@@ -11,12 +11,12 @@
 - batalkan penghapusan unduhan #1472
 - Opsi unduh di menu berbagi #1498
 - Menambahkan opsi berbagi ke menu ketuk panjang #1454
-- Minimalkan pemain utama di pintu keluar #1354
-- Pembaruan versi perpustakaan dan perbaikan cadangan basis data #1510
+- Minimalkan pemutar utama saat keluar #1354
+- Pembaruan versi library dan perbaikan cadangan basis data #1510
 - Pembaruan ExoPlayer 2.8.2 #1392
   - Mengolah ulang dialog kontrol kecepatan pemutaran untuk mendukung ukuran langkah yang berbeda untuk perubahan kecepatan yang lebih cepat.
   - Menambahkan tombol untuk maju cepat selama hening di kontrol kecepatan pemutaran. Ini akan berguna untuk buku audio dan genre musik tertentu, dan dapat menghadirkan pengalaman yang benar-benar mulus (dan dapat memecah lagu dengan banyak keheningan =\\).
-  - Resolusi sumber media yang difaktorkan ulang untuk memungkinkan penyampaian metadata bersama media secara internal di pemutar, daripada melakukannya secara manual. Sekarang kami memiliki satu sumber metadata dan langsung tersedia saat pemutaran dimulai.
+  - Refaktor ulang resolusi sumber media untuk memungkinkan penyampaian metadata bersama media secara internal di pemutar, daripada melakukannya secara manual. Sekarang kami memiliki satu sumber metadata dan langsung tersedia saat pemutaran dimulai.
   - Memperbaiki metadata daftar putar jarak jauh yang tidak diperbarui ketika metadata baru tersedia ketika fragmen daftar putar dibuka.
   - Berbagai perbaikan UI: #1383, kontrol notifikasi pemutar latar belakang kini selalu berwarna putih, lebih mudah untuk mematikan pemutar popup melalui lemparan
 - Gunakan ekstraktor baru dengan arsitektur yang difaktorkan ulang untuk multilayanan
@@ -25,9 +25,9 @@
 
 - Perbaiki #1440 Tata Letak Info Video Rusak #1491
 - Lihat perbaikan riwayat #1497
-  - #1495, dengan memperbarui metadata (thumbnail, judul, dan jumlah video) segera setelah pengguna mengakses playlist.
+  - #1495, dengan memperbarui metadata (thumbnail, judul, dan jumlah video) segera setelah pengguna mengakses daftar putar.
   - #1475, dengan mendaftarkan tampilan di database saat pengguna memulai video di pemutar eksternal pada fragmen detail.
 - Perbaiki batas waktu layar jika ada mode popup. #1463 (Diperbaiki #640)
 - Perbaikan pemutar video utama #1509
-  - [#1412] Memperbaiki mode pengulangan yang menyebabkan NPE pemain ketika niat baru diterima saat aktivitas pemain berada di latar belakang.
-  - Memperbaiki meminimalkan pemain ke popup tidak menghancurkan pemain ketika izin popup tidak diberikan.
+  - [#1412] Memperbaiki mode pengulangan yang menyebabkan pemutar NPE ketika intent baru diterima saat aktivitas pemutar berada di latar belakang.
+  - Perbaiki meminimalkan pemutar ke popup tidak menghancurkan pemutar ketika izin popup tidak diberikan.
diff --git a/fastlane/metadata/android/id/changelogs/972.txt b/fastlane/metadata/android/id/changelogs/972.txt
index e6b705eeb..99990cc90 100644
--- a/fastlane/metadata/android/id/changelogs/972.txt
+++ b/fastlane/metadata/android/id/changelogs/972.txt
@@ -11,4 +11,4 @@ Memperbarui instance Invidious dan mendukung tautan Piped.
 
 Diperbaiki
 [YouTube] Konten yang dibatasi usia
-Cegah jendela bocor Pengecualian saat membuka dialog pilihan
+Cegah Pengecualian jendela bocor saat membuka dialog pilihan
diff --git a/fastlane/metadata/android/id/changelogs/975.txt b/fastlane/metadata/android/id/changelogs/975.txt
index 65e6bcebd..b8f4aca1f 100644
--- a/fastlane/metadata/android/id/changelogs/975.txt
+++ b/fastlane/metadata/android/id/changelogs/975.txt
@@ -1,17 +1,17 @@
 Baru
-• Tampilkan pratinjau thumbnail saat mencari
-• Mendeteksi komentar yang dinonaktifkan
-• Izinkan menandai item feed sebagai ditonton
-• Tampilkan komentar hati
+• Menampilkan Thumbnail ketika Mempercepat
+• Deteksi komentar yang dimatikan
+• Penandaan item feed sebagai ditonton
+• Menampilkan Komentar disukai
 
-Ditingkatkan
-• Memperbaiki metadata dan tata letak tag
-• Menerapkan warna layanan ke komponen UI
+Peningkatan
+• Pembaruan Layout metadata dan tag
+• Penerapan warna layanan pada komponen UI
 
-Tetap
-• Perbaiki thumbnail di mini player
-• Memperbaiki buffering tanpa akhir pada item antrian duplikat
-• Beberapa perbaikan pemain seperti rotasi dan penutupan lebih cepat
-• Perbaiki ReCAPTCHA yang tersisa dimuat di latar belakang
-• Nonaktifkan klik saat menyegarkan feed
-• Memperbaiki beberapa pengunduh yang mogok
+Perbaikan
+• Perbaiki thumbnail pada mini player
+• Perbaiki buffering tanpa akhir pada item antrian duplikat
+• Perbaikan Beberapa player rotasi layar dan penutupan yang lebih cepat
+• Perbaiki ReCaptcha tetap termuat di latar belakang
+• Matikan klik saat memuat feed
+• Perbaiki beberapa kerusakan pengunduh
diff --git a/fastlane/metadata/android/id/changelogs/976.txt b/fastlane/metadata/android/id/changelogs/976.txt
index d76b10e91..d3c7e505b 100644
--- a/fastlane/metadata/android/id/changelogs/976.txt
+++ b/fastlane/metadata/android/id/changelogs/976.txt
@@ -1,9 +1,9 @@
-• Ditambahkan opsi untuk langsung membuka pemutar dalam layar penuh
+• Tambahkan opsi untuk langsung membuka pemutar dalam layar penuh
 • Izinkan memilih jenis saran pencarian yang akan ditampilkan
 • Tema gelap kini lebih gelap + layar splash gelap ditambahkan
-• Peningkatan pemilih file untuk menghilangkan file yang tidak diinginkan
+• Peningkatan pemilih file untuk menyembunyikan file yang tidak diinginkan
 • Memperbaiki impor langganan YouTube
-• Memutar ulang streaming memerlukan ketukan pada tombol putar ulang lagi
+• Memutar ulang stream memerlukan ketukan pada tombol putar ulang lagi
 • Memperbaiki sesi audio penutup
 • [Android TV] Memperbaiki lompatan seekbar yang panjang saat menggunakan DPad
 
diff --git a/fastlane/metadata/android/id/changelogs/995.txt b/fastlane/metadata/android/id/changelogs/995.txt
index 277b2468a..b3db15bf1 100644
--- a/fastlane/metadata/android/id/changelogs/995.txt
+++ b/fastlane/metadata/android/id/changelogs/995.txt
@@ -4,13 +4,13 @@ Baru
 • Dapatkan URL ke semua gambar
 
 Ditingkatkan
-• Aksesibilitas antarmuka pemain
-• Pilihan audio yang lebih baik untuk download video saja
-• Pilihan untuk memasukkan nama playlist dan video ke konten playlist bersama
+• Aksesibilitas antarmuka pemutar
+• Pilihan audio yang lebih baik untuk unduhan video saja
+• Pilihan untuk memasukkan nama playlist dan video ke konten daftar putar bersama
 
-Tetap
+Diperbaiki
 • [YouTube] Perbaiki penghitungan jumlah suka
-• Memperbaiki pemain yang tidak merespons popup dan crash
+• Perbaiki pemutar yang tidak merespons popup dan crash
 • Pemilihan bahasa yang salah di pemilih bahasa
 • Fokus audio pemutar tidak menerapkan mode senyap
-• Penambahan item playlist terkadang tidak berfungsi
+• Penambahan item daftar putar terkadang tidak berfungsi
diff --git a/fastlane/metadata/android/id/changelogs/996.txt b/fastlane/metadata/android/id/changelogs/996.txt
new file mode 100644
index 000000000..d1fd9d5b7
--- /dev/null
+++ b/fastlane/metadata/android/id/changelogs/996.txt
@@ -0,0 +1,2 @@
+Perbaiki NullPointerException saat membuka saluran / konferensi di media.ccc.de.
+The Grinch mencoba untuk merusak hadiah Natal kami ke anda, tapi kami memperbaikinya.
diff --git a/fastlane/metadata/android/it/changelogs/996.txt b/fastlane/metadata/android/it/changelogs/996.txt
new file mode 100644
index 000000000..0d69a3264
--- /dev/null
+++ b/fastlane/metadata/android/it/changelogs/996.txt
@@ -0,0 +1,2 @@
+Corretto un NullPointerException quando si apre un canale / conferenza in media.ccc.de.
+Il Grinch ha cercato di rompere il nostro regalo di Natale per te, ma l'abbiamo risolto.
diff --git a/fastlane/metadata/android/ja/changelogs/954.txt b/fastlane/metadata/android/ja/changelogs/954.txt
index e927e8e51..6b0da1598 100644
--- a/fastlane/metadata/android/ja/changelogs/954.txt
+++ b/fastlane/metadata/android/ja/changelogs/954.txt
@@ -1,6 +1,6 @@
 ・新しいアプリケーションの利用体系: 詳細ページで動画を再生し、下にスワイプしてプレイヤーを最小化する
 ・MediaStyle 通知: 通知のカスタマイズ可能なアクション、パフォーマンスの向上
-・デスクトップアプリのような  NewPipe 使用中のリサイズ
+・デスクトップアプリのような NewPipe 使用中のリサイズ
 
 ・サポートしていない URL のとき他のアプリで開くオプションを表示する
 ・取得できなかった場合の検索サジェストのユーザー体験の向上
diff --git a/fastlane/metadata/android/ka/changelogs/64.txt b/fastlane/metadata/android/ka/changelogs/64.txt
index 5d32b7c0d..de50608ab 100644
--- a/fastlane/metadata/android/ka/changelogs/64.txt
+++ b/fastlane/metadata/android/ka/changelogs/64.txt
@@ -1,5 +1,5 @@
 ### გაუმჯობესებები
- - დაემატა ვიდეოს ხარისხის შეზღუდვის შესაძლებლობა მობილური ინტერნეტის გამოყენებისას.  #1339
+ - დაემატა ვიდეოს ხარისხის შეზღუდვის შესაძლებლობა მობილური ინტერნეტის გამოყენებისას. #1339
  - დაიმახსოვრე სიკაშკაშე #1442 სესიისთვის
  - გააუმჯობესეთ ჩამოტვირთვის შესრულება სუსტი CPU-ებისთვის #1431
  - დაამატეთ (სამუშაო) მხარდაჭერა მედია სესიისთვის #1433
diff --git a/fastlane/metadata/android/ka/changelogs/69.txt b/fastlane/metadata/android/ka/changelogs/69.txt
index b5d54c565..4bc9f3c03 100644
--- a/fastlane/metadata/android/ka/changelogs/69.txt
+++ b/fastlane/metadata/android/ka/changelogs/69.txt
@@ -11,7 +11,7 @@
  - დაამატეთ მხარდაჭერა ლოკალიზაციის #1792-ისთვის
 
  ### ასწორებს
- - დააფიქსირეთ დროის ანალიზი .  ფორმატში, ამიტომ NewPipe შეიძლება გამოყენებულ იქნას ფინეთში
+ - დააფიქსირეთ დროის ანალიზი . ფორმატში, ამიტომ NewPipe შეიძლება გამოყენებულ იქნას ფინეთში
  - შეასწორეთ გამოწერების რაოდენობა
  - დაამატეთ წინა პლანზე სერვისის ნებართვა API 28+ მოწყობილობებისთვის #1830
 
diff --git a/fastlane/metadata/android/ka/changelogs/740.txt b/fastlane/metadata/android/ka/changelogs/740.txt
index 3589197f9..b6f13456b 100644
--- a/fastlane/metadata/android/ka/changelogs/740.txt
+++ b/fastlane/metadata/android/ka/changelogs/740.txt
@@ -3,11 +3,11 @@
  <li>გახადეთ კომენტარებში ბმულები დაწკაპუნებით, გაზარდეთ ტექსტის ზომა</li>
  <li>მოძებნეთ კომენტარებში დროის ანაბეჭდის ბმულებზე დაწკაპუნებით</li>
  <li>სასურველი ჩანართის ჩვენება ახლახან არჩეული მდგომარეობის მიხედვით</li>
- <li>დაამატე დასაკრავი სია რიგში, როდესაც დიდხანს დააწკაპუნებთ &#39;ფონზე&#39;  დასაკრავი სიის ფანჯარაში</li>
+ <li>დაამატე დასაკრავი სია რიგში, როდესაც დიდხანს დააწკაპუნებთ &#39;ფონზე&#39; დასაკრავი სიის ფანჯარაში</li>
  <li>მოძებნეთ გაზიარებული ტექსტი, როდესაც ის არ არის URL</li>
- <li>დაამატეთ &quot;გაზიარება მიმდინარე დროს&quot;  ღილაკი მთავარ ვიდეო დამკვრელზე</li>
+ <li>დაამატეთ &quot;გაზიარება მიმდინარე დროს&quot; ღილაკი მთავარ ვიდეო დამკვრელზე</li>
  <li>დახურვის ღილაკის დამატება მთავარ დამკვრელზე, როდესაც ვიდეო რიგი დასრულდება</li>
- <li>დაამატეთ &quot;დაკვრა პირდაპირ ფონზე&quot;  ვიდეო სიის ელემენტების მენიუს ხანგრძლივი დაჭერისთვის</li>
+ <li>დაამატეთ &quot;დაკვრა პირდაპირ ფონზე&quot; ვიდეო სიის ელემენტების მენიუს ხანგრძლივი დაჭერისთვის</li>
  <li>გააუმჯობესეთ ინგლისური თარგმანი Play/Enqueue ბრძანებებისთვის</li>
  <li>შესრულების მცირე გაუმჯობესება</li>
  <li>გამოუყენებელი ფაილების წაშლა</li>
diff --git a/fastlane/metadata/android/ka/changelogs/850.txt b/fastlane/metadata/android/ka/changelogs/850.txt
index c78859a4e..7f6cc55e2 100644
--- a/fastlane/metadata/android/ka/changelogs/850.txt
+++ b/fastlane/metadata/android/ka/changelogs/850.txt
@@ -1 +1 @@
-ამ გამოშვებაში განახლდა YouTube ვებსაიტის ვერსია.  ვებსაიტის ძველი ვერსია შეწყდება მარტში და, შესაბამისად, თქვენ უნდა განაახლოთ NewPipe.
+ამ გამოშვებაში განახლდა YouTube ვებსაიტის ვერსია. ვებსაიტის ძველი ვერსია შეწყდება მარტში და, შესაბამისად, თქვენ უნდა განაახლოთ NewPipe.
diff --git a/fastlane/metadata/android/ka/changelogs/967.txt b/fastlane/metadata/android/ka/changelogs/967.txt
index 5d1835b2e..232586a73 100644
--- a/fastlane/metadata/android/ka/changelogs/967.txt
+++ b/fastlane/metadata/android/ka/changelogs/967.txt
@@ -1 +1 @@
-გაასწორა YouTube, რომელიც ევროკავშირში არ მუშაობს გამართულად.  ეს გამოწვეული იყო ახალი ქუქი-ჩანაწერით და კონფიდენციალურობის თანხმობის სისტემით, რომელიც მოითხოვს NewPipe-ს დააყენოს თანხმობის ქუქი-ფაილები.
+გაასწორა YouTube, რომელიც ევროკავშირში არ მუშაობს გამართულად. ეს გამოწვეული იყო ახალი ქუქი-ჩანაწერით და კონფიდენციალურობის თანხმობის სისტემით, რომელიც მოითხოვს NewPipe-ს დააყენოს თანხმობის ქუქი-ფაილები.
diff --git a/fastlane/metadata/android/ka/changelogs/978.txt b/fastlane/metadata/android/ka/changelogs/978.txt
index 600962d14..787d5d12e 100644
--- a/fastlane/metadata/android/ka/changelogs/978.txt
+++ b/fastlane/metadata/android/ka/changelogs/978.txt
@@ -1 +1 @@
-დაფიქსირდა შემოწმების შესრულება NewPipe-ის ახალი ვერსიისთვის.  ეს შემოწმება ხანდახან ძალიან ადრე სრულდებოდა და, შესაბამისად, იწვევს აპის ავარიას.  ეს ახლა უნდა გამოსწორდეს.
+დაფიქსირდა შემოწმების შესრულება NewPipe-ის ახალი ვერსიისთვის. ეს შემოწმება ხანდახან ძალიან ადრე სრულდებოდა და, შესაბამისად, იწვევს აპის ავარიას. ეს ახლა უნდა გამოსწორდეს.
diff --git a/fastlane/metadata/android/ka/changelogs/988.txt b/fastlane/metadata/android/ka/changelogs/988.txt
index e42877c9c..3c74e29f5 100644
--- a/fastlane/metadata/android/ka/changelogs/988.txt
+++ b/fastlane/metadata/android/ka/changelogs/988.txt
@@ -1,2 +1,2 @@
 [YouTube] გაასწორდა შეცდომა „ვერ მივიღე ნაკადი“ რომელიმე ვიდეოს დაკვრის მცდელობისას
- [YouTube] შეასწორდა „შემდეგი კონტენტი მიუწვდომელია ამ აპში“.  მოთხოვნილი ვიდეოს ნაცვლად ნაჩვენები შეტყობინება
+ [YouTube] შეასწორდა „შემდეგი კონტენტი მიუწვდომელია ამ აპში“. მოთხოვნილი ვიდეოს ნაცვლად ნაჩვენები შეტყობინება
diff --git a/fastlane/metadata/android/ka/full_description.txt b/fastlane/metadata/android/ka/full_description.txt
index eff57aabe..e65267b46 100644
--- a/fastlane/metadata/android/ka/full_description.txt
+++ b/fastlane/metadata/android/ka/full_description.txt
@@ -1 +1 @@
-NewPipe არ იყენებს Google Framework ბიბლიოთეკას ან YouTube API-ს.  ის მხოლოდ აანალიზებს ვებსაიტს, რათა მოიპოვოს მისთვის საჭირო ინფორმაცია.  ამიტომ ამ აპლიკაციის გამოყენება შესაძლებელია მოწყობილობებზე Google სერვისების დაყენების გარეშე.  გარდა ამისა, თქვენ არ გჭირდებათ YouTube ანგარიში NewPipe-ის გამოსაყენებლად და ეს არის FLOSS.
+NewPipe არ იყენებს Google Framework ბიბლიოთეკას ან YouTube API-ს. ის მხოლოდ აანალიზებს ვებსაიტს, რათა მოიპოვოს მისთვის საჭირო ინფორმაცია. ამიტომ ამ აპლიკაციის გამოყენება შესაძლებელია მოწყობილობებზე Google სერვისების დაყენების გარეშე. გარდა ამისა, თქვენ არ გჭირდებათ YouTube ანგარიში NewPipe-ის გამოსაყენებლად და ეს არის FLOSS.
diff --git a/fastlane/metadata/android/pa/changelogs/996.txt b/fastlane/metadata/android/pa/changelogs/996.txt
new file mode 100644
index 000000000..ac5940bc3
--- /dev/null
+++ b/fastlane/metadata/android/pa/changelogs/996.txt
@@ -0,0 +1,2 @@
+media.ccc.de ਵਿੱਚ ਇੱਕ ਚੈਨਲ / ਕਾਨਫਰੰਸ ਖੋਲ੍ਹਣ ਵੇਲੇ ਇੱਕ NullPointerException ਫਿਕਸ ਕੀਤਾ ਗਿਆ।
+ਗ੍ਰਿੰਚ ਨੇ ਤੁਹਾਡੇ ਲਈ ਸਾਡੇ ਕ੍ਰਿਸਮਸ ਤੋਹਫ਼ੇ ਨੂੰ ਤੋੜਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ, ਪਰ ਅਸੀਂ ਇਸਨੂੰ ਠੀਕ ਕਰ ਦਿੱਤਾ।
diff --git a/fastlane/metadata/android/pt-PT/changelogs/995.txt b/fastlane/metadata/android/pt-PT/changelogs/995.txt
new file mode 100644
index 000000000..b22d3122a
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/995.txt
@@ -0,0 +1,16 @@
+Novo
+- Separadores de canais de suporte
+- Selecionar a qualidade da imagem
+- Obter URLs para todas as imagens
+
+Melhorado
+- Acessibilidade das interfaces do leitor
+- Melhor seleção de áudio para transferências apenas de vídeo
+- Opção para incluir nomes de listas de reprodução e de vídeos no conteúdo de listas de reprodução partilhadas
+
+Corrigido
+- YouTube] Correção da contagem de gostos
+- Correção de popups e falhas do leitor que não responde
+- Seleção de idiomas errados no seletor de idiomas
+- A focagem do áudio do leitor não respeitava o silêncio
+- A adição de itens à lista de reprodução não funcionava ocasionalmente.
diff --git a/fastlane/metadata/android/pt/changelogs/995.txt b/fastlane/metadata/android/pt/changelogs/995.txt
index ebe4e742f..db7a18347 100644
--- a/fastlane/metadata/android/pt/changelogs/995.txt
+++ b/fastlane/metadata/android/pt/changelogs/995.txt
@@ -5,10 +5,12 @@ Novo
 
 Melhorado
 - Acessibilidade das interfaces do leitor
-- Opção para adicionar o nome da lista de reprodução e o nome do vídeo ao conteúdo de partilha da lista de reprodução
-- Melhorias internas e atualizações de dependências
+- Melhor seleção de áudio para transferências apenas de vídeo
+- Opção para incluir nomes de listas de reprodução e de vídeos no conteúdo de listas de reprodução partilhadas
 
 Corrigido
+- YouTube] Correção da contagem de gostos
+- Correção de popups e falhas do leitor que não responde
 - Seleção de idiomas errados no seletor de idiomas
-- O foco do áudio do leitor não estava a respeitar o silêncio
-- A adição de itens a listas de reprodução não funcionava em casos específicos
+- A focagem do áudio do leitor não estava a respeitar o silêncio
+- A adição de itens à lista de reprodução não funcionava ocasionalmente.
diff --git a/fastlane/metadata/android/pt/changelogs/996.txt b/fastlane/metadata/android/pt/changelogs/996.txt
new file mode 100644
index 000000000..4f1c8f229
--- /dev/null
+++ b/fastlane/metadata/android/pt/changelogs/996.txt
@@ -0,0 +1,2 @@
+Foi corrigida uma NullPointerException ao abrir um canal/conferência em media.ccc.de.
+O Grinch tentou estragar a nossa prenda de Natal, mas nós resolvemos o problema.
diff --git a/fastlane/metadata/android/ru/changelogs/995.txt b/fastlane/metadata/android/ru/changelogs/995.txt
index dcefd106a..11b54b662 100644
--- a/fastlane/metadata/android/ru/changelogs/995.txt
+++ b/fastlane/metadata/android/ru/changelogs/995.txt
@@ -1,14 +1,16 @@
-Из нового
+Новое
 • Поддержка вкладок каналов
 • Выбор качества изображения
 • Получение URL всех изображений
 
-Улучшения
-• Доступность интерфейсов проигрывателей
-• Добавлена возможность добавлять название подборки и название видеораспространяемого содержимого подборки.
-• Внутренние усовершенствования и обновление зависимостей
+Улучшено
+• Доступность интерфейсов плееров
+• Выбор аудио для скачивания только видео
+• Возможность включения названий плейлистов и видео в общий плейлист
 
-Исправления
+Исправлено
+• Получение кол-ва лайков
+• Плеер не видит всплывающие окона и вылетает
 • Выбор неправильных языков в переключателе языков
 • Фокусировка звука проигрывателя не учитывала выключение звука
-• Добавление элементов в подборки не работало в определённых случаях
+• Добавление элемента плейлиста иногда не работает
diff --git a/fastlane/metadata/android/ru/changelogs/996.txt b/fastlane/metadata/android/ru/changelogs/996.txt
new file mode 100644
index 000000000..d516b0bc2
--- /dev/null
+++ b/fastlane/metadata/android/ru/changelogs/996.txt
@@ -0,0 +1,2 @@
+Исправлена ошибка NullPointerException при открытии канала/конференции в media.ccc.de.
+Гринч пытался сломать наш рождественский подарок вам, но мы починили его.
diff --git a/fastlane/metadata/android/si/full_description.txt b/fastlane/metadata/android/si/full_description.txt
new file mode 100644
index 000000000..c5300a7e9
--- /dev/null
+++ b/fastlane/metadata/android/si/full_description.txt
@@ -0,0 +1 @@
+NewPipe කිසිදු Google රාමු පුස්තකාලයක් හෝ YouTube API භාවිතා නොකරයි. එය අවශ්‍ය තොරතුරු ලබා ගැනීම සඳහා වෙබ් අඩවිය විග්‍රහ කරයි. එබැවින් මෙම යෙදුම Google සේවා ස්ථාපනය කර නොමැති උපාංග මත භාවිතා කළ හැක. එසේම, ඔබට NewPipe භාවිතා කිරීමට YouTube ගිණුමක් අවශ්‍ය නොවන අතර එය FLOSS වේ.
diff --git a/fastlane/metadata/android/si/short_description.txt b/fastlane/metadata/android/si/short_description.txt
new file mode 100644
index 000000000..6f79e214d
--- /dev/null
+++ b/fastlane/metadata/android/si/short_description.txt
@@ -0,0 +1 @@
+Android සඳහා නොමිලේ සැහැල්ලු YouTube ඉදිරි අන්තය.
diff --git a/fastlane/metadata/android/sk/changelogs/995.txt b/fastlane/metadata/android/sk/changelogs/995.txt
new file mode 100644
index 000000000..9729b3f70
--- /dev/null
+++ b/fastlane/metadata/android/sk/changelogs/995.txt
@@ -0,0 +1,16 @@
+Nové
+- Podpora kariet kanálov
+- Výber kvality obrázka
+- Získate adresy URL všetkých obrázkov
+
+Vylepšené
+- Prístupnosť rozhrania prehrávača
+- Lepší výber zvuku pri sťahovaní videa
+- Možnosť zahrnúť názvy playlistov a videí do zdieľaného obsahu playlistov
+
+Opravené
+- [YouTube] Oprava získavania počtu lajkov
+- Oprava vyskakovacích okien a pádov prehrávača
+- Výber nesprávnych jazykov vo výbere jazyka
+- Zameranie zvuku prehrávača nerešpektovalo stlmenie
+- Občas nefungovalo pridávanie položiek do playlistu
diff --git a/fastlane/metadata/android/sk/changelogs/996.txt b/fastlane/metadata/android/sk/changelogs/996.txt
new file mode 100644
index 000000000..25fa111b5
--- /dev/null
+++ b/fastlane/metadata/android/sk/changelogs/996.txt
@@ -0,0 +1,2 @@
+Opravená výnimka NullPointerException pri otvorení kanála/konferencie v media.ccc.de.
+Grinch sa pokúsil prekaziť náš vianočný darček pre vás, ale opravili sme to.
diff --git a/fastlane/metadata/android/sr/changelogs/996.txt b/fastlane/metadata/android/sr/changelogs/996.txt
new file mode 100644
index 000000000..749b74624
--- /dev/null
+++ b/fastlane/metadata/android/sr/changelogs/996.txt
@@ -0,0 +1,2 @@
+Поправљен NullPointerException приликом отварања канала/конференције у media.ccc.de.
+Гринч је покушао да вам поквари наш божићни поклон, али смо то поправили.
diff --git a/fastlane/metadata/android/sv/changelogs/63.txt b/fastlane/metadata/android/sv/changelogs/63.txt
index dcf52354b..51c6551c1 100644
--- a/fastlane/metadata/android/sv/changelogs/63.txt
+++ b/fastlane/metadata/android/sv/changelogs/63.txt
@@ -1,7 +1,7 @@
 ### Förbättringar
-- Import/export inställningar #1333
+- Import/exportinställningar #1333
 - Minskade överdragningen (prestandaförbättring) #1371
-- Små kod förbättringar #1375
+- Små kodförbättringar #1375
 - La till allt om GDPR #1420
 
 ### Fixade
diff --git a/fastlane/metadata/android/sv/changelogs/64.txt b/fastlane/metadata/android/sv/changelogs/64.txt
index f874eebff..182fc0156 100644
--- a/fastlane/metadata/android/sv/changelogs/64.txt
+++ b/fastlane/metadata/android/sv/changelogs/64.txt
@@ -1,8 +1,8 @@
 ### Förbättringar
-- La till möjligheten all begränsa video kvaliteten vid användning av mobildata. #1339
+- La till möjligheten all begränsa videokvaliteten vid användning av mobildata. #1339
 - Kom ihåg ljusstyrka för sessionen #1442
-- Förbättra nedladdnings prestandan på svagare CPUs #1431
-- La till (fungerande) support för media sessioner #1433
+- Förbättra nedladdningsprestandan på svagare CPUs #1431
+- La till (fungerande) support för mediasessioner #1433
 
 ### Fixade
 - Fixade krasch vid öppning av nedladdningar (fixen är nu tillgänglig för release builds) #1441
diff --git a/fastlane/metadata/android/sv/changelogs/957.txt b/fastlane/metadata/android/sv/changelogs/957.txt
new file mode 100644
index 000000000..a8be4f09b
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/957.txt
@@ -0,0 +1,10 @@
+• Förenade specifika kö åtgärder
+• Två finger gest att stänga spelare
+• Tillåt rensning av reCAPTCHA-cookies
+• Inställning för att inte färglägga meddelande
+• Förbättra hur videoinformation öppnas för att fixa oändlig buffring, buggigt beteende när du delar till NewPipe och andra inkonsekvenser
+• Snabba upp YouTube-videor och fixa åldersbegränsning
+• Fixa krasch vid snabb fram/bak spolning
+• Ordna inte listor genom att dra miniatyrer
+• Kom alltid ihåg popup egenskaper
+• Lägga till Santali språk
diff --git a/fastlane/metadata/android/sv/changelogs/958.txt b/fastlane/metadata/android/sv/changelogs/958.txt
new file mode 100644
index 000000000..268362f87
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/958.txt
@@ -0,0 +1,10 @@
+Nytt och förbättrat:
+• La till inställning för att dölja miniatyr på låsskärmen
+• Dra för att uppdatera flöde
+• Förbättrad prestanda vid hämtning av lokala listor
+
+Fixat:
+• Fixade krasch när du startar NewPipe efter att den togs bort från RAM
+• Fixade krasch vid start när det inte finns någon internetanslutning
+• Fixade respekt för ljusstyrka- och volymgestinställningar
+• [YouTube] Fixade långa spellistor
diff --git a/fastlane/metadata/android/sv/changelogs/961.txt b/fastlane/metadata/android/sv/changelogs/961.txt
new file mode 100644
index 000000000..6e1abc229
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/961.txt
@@ -0,0 +1,12 @@
+• [YouTube] Mix stöd
+• [YouTube] Visa information om offentliga programföretag och Covid-19
+• [media.ccc.de] La till nya videor
+• La till somalisk översättning
+
+• Många interna förbättringar
+
+• Fixade delning av videor ifrån spelaren
+• Fixade blank ReCaptcha webview
+• Fixade krasch som inträffade när du tar bort en ström från en lista
+• [PeerTube] Fixade relaterade strömmar
+• [YouTube] Fixade YouTube Musiksökning
diff --git a/fastlane/metadata/android/sv/changelogs/964.txt b/fastlane/metadata/android/sv/changelogs/964.txt
new file mode 100644
index 000000000..cbdc9f4ff
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/964.txt
@@ -0,0 +1,7 @@
+• La till stöd för kapitel i spelarkontroller
+• [PeerTube] La till Sepia-sökning
+• La till dela knappen i videodetaljvy och flyttad strömbeskrivning till flik layouten
+• Inaktivera återställande av ljusstyrka om ljusstyrks gest är inaktiverad
+• Lägg till listobjekt för att spela video på kodi
+• Fixade krasch när ingen standardwebbläsare är inställd på vissa enheter och förbättrade dela dialog
+• Växla spela/pausa med hårdvaru mellanslags knapp i fullskärm
diff --git a/fastlane/metadata/android/sv/changelogs/965.txt b/fastlane/metadata/android/sv/changelogs/965.txt
new file mode 100644
index 000000000..9b298947a
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/965.txt
@@ -0,0 +1,6 @@
+Fixade krasch som inträffade vid förflyttning av kanalgrupper.
+Fixade hämtning av fler YouTube videor från kanaler och spellistor.
+Fixade hämtning av YouTube kommentarer.
+La till stöd för /watch /, /v / och /w / subpaths i YouTube adresser.
+Fixade extraktion av SoundCloud klient id och geobegränsat innehåll.
+La till nordkurdisk lokalisering.
diff --git a/fastlane/metadata/android/sv/changelogs/966.txt b/fastlane/metadata/android/sv/changelogs/966.txt
new file mode 100644
index 000000000..b8e183604
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/966.txt
@@ -0,0 +1,14 @@
+Nytt:
+• La till en ny tjänst: Bandcamp
+
+Förbättrat:
+• Lägg till en inställning för att appen ska följa enhetstemat
+• Förhindra vissa kraschar genom att visa en förbättrad felpanel
+• Visa mer information om varför innehåll är otillgängligt
+• Hårdvaru mellanslagsknapp ändrar spela/pausa
+• Visa "Nedladdning startad" toast
+
+Fixat:
+• Fixade liten miniatyr i videodetaljer medan du spelar i bakgrunden
+• Fixade tom titel i minimerad spelare
+• Fixade senaste storleksläge som inte återställdes korrekt
diff --git a/fastlane/metadata/android/sv/changelogs/968.txt b/fastlane/metadata/android/sv/changelogs/968.txt
new file mode 100644
index 000000000..4582309f4
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/968.txt
@@ -0,0 +1,7 @@
+La till kanalinformationsalternativ i långtrycksmenyn.
+La till funktionalitet för att byta namn från spellistans gränssnitt.
+Låt användaren pausa medan en video buffrar.
+Polerade det ljusa temat.
+Fixade överlappande teckensnitt när du använder en större teckenstorlek.
+Fixade ingen video på Formuler och Zephier enheter.
+Fixade olika kraschar.
diff --git a/fastlane/metadata/android/sv/changelogs/970.txt b/fastlane/metadata/android/sv/changelogs/970.txt
new file mode 100644
index 000000000..020ee2c1b
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/970.txt
@@ -0,0 +1,11 @@
+Nytt
+• Visa innehåll metadata (taggar, kategorier, licens, ...) under beskrivningen
+• Lägg till alternativet "Visa kanalinformation" i distans (icke-lokala) spellistor
+• Lägg till "Öppna i webbläsare" -alternativ för långtrycksmeny
+
+Fixat
+• Fixade rotations krasch på video detalj sida
+• Fixade "Spela med Kodi" knappen i spelaren som alltid uppmanade till att installera Kore
+• Fixade och förbättrad inställning av import och exportvägar
+• [YouTube] Fixade kommentarers gilla antal
+Och mycket mer
diff --git a/fastlane/metadata/android/sv/changelogs/975.txt b/fastlane/metadata/android/sv/changelogs/975.txt
new file mode 100644
index 000000000..f280c3830
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/975.txt
@@ -0,0 +1,15 @@
+Nytt
+• Visa en miniatyr förhandsvisning medan du söker
+• Upptäck avstängda kommentarer
+• Tillåt markering av ett flödesobjekt som visat
+• Visa kommentar hjärtan
+
+Förbättrat
+• Förbättrade metadata och tagg layout
+• Applicera service färg till UI komponenter
+
+Fixat
+• Fixade miniatyrbild i minispelare
+• Fixade oändlig buffring på dubbla köartiklar
+• Vissa spelare fixar som rotation och snabbare stängning
+• Fixade ReCAPTCHA som var kvar i bakgrunden
diff --git a/fastlane/metadata/android/sv/changelogs/976.txt b/fastlane/metadata/android/sv/changelogs/976.txt
new file mode 100644
index 000000000..96b005170
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/976.txt
@@ -0,0 +1,9 @@
+• La till alternativ för att direkt öppna spelare i fullskärm
+• Tillåt användaren att välja vilka typer av sökförslag som ska visas
+• Mörkt tema är nu mörkare + mörk startskärm tillagd
+• Förbättrade fil väljare att gråa ut oönskade filer
+• Fixade import av YouTube prenumerationer
+• Att spela en ström kräver tryck på spela knappen igen
+• [Android TV] Fixa långa sökbar hopp vid användning av en DPad
+
+För att se ytterligare ändringar, se ändringsloggen (och blogginlägg) från Länkfliken nedan.
diff --git a/fastlane/metadata/android/sv/changelogs/983.txt b/fastlane/metadata/android/sv/changelogs/983.txt
new file mode 100644
index 000000000..edc397a29
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/983.txt
@@ -0,0 +1,9 @@
+La till nytt dubbel tryck för att söka UI och beteende
+Gör inställningar sökbara
+Markera fäst kommentarer som sådana
+Lägg till öppna med app stöd för FSFE PeerTube instans
+Lägg till felmeddelanden
+Fixade repris av första kö objekt vid spelar förändring
+Vänta längre vid buffring under livestreams innan misslyckande
+Fixade ordning av lokala sökresultat
+Fixade tomma objektfält i spelkö
diff --git a/fastlane/metadata/android/sv/changelogs/986.txt b/fastlane/metadata/android/sv/changelogs/986.txt
new file mode 100644
index 000000000..2ebaacaa8
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/986.txt
@@ -0,0 +1,15 @@
+Nytt
+• Avisering för nya strömningar
+• Sömlös övergång mellan bakgrund och videospelare
+• Ändra pitch av semitoner
+• Lägg till huvudspelarens kö i en spellista
+
+Förbättrat
+• Kom ihåg hastighet/pitch steg storlek
+• Mildra initial lång buffring i videospelaren
+• Förbättra spelarens UI för Android TV
+• Bekräfta innan borttagning av alla nedladdade filer
+
+Fixat
+• Fixade media knapp som inte gömde spelarkontroller
+• Fixade uppspelningsåterställning vid spelar förändring
diff --git a/fastlane/metadata/android/sv/changelogs/987.txt b/fastlane/metadata/android/sv/changelogs/987.txt
new file mode 100644
index 000000000..59416e59e
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/987.txt
@@ -0,0 +1,12 @@
+Nytt
+• Stöd andra leveransmetoder än progressiv HTTP: snabbare uppspelningstid, fixar för PeerTube och SoundCloud, uppspelning av nyligen avslutade YouTube strömmar
+• La till knapp för att lägga till en fjärrspellista till en lokal
+• Bildförhandsvisning i Android 10+ dela blad
+
+Förbättrat
+• Förbättrade uppspelningsparametrar dialog
+• Flytta abonnemang import/exportknappar till trepunktsmenyn
+
+Fixat
+• Fixade ta bort tittade videor från spellista
+• Fixade dela meny tema och "lägg till i spellista"
diff --git a/fastlane/metadata/android/sv/changelogs/988.txt b/fastlane/metadata/android/sv/changelogs/988.txt
new file mode 100644
index 000000000..09335ecbc
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/988.txt
@@ -0,0 +1,2 @@
+[YouTube] Fixade "Kan inte få någon ström" fel vid spelning av någon video
+[YouTube] Fixade "Det följande innehållet är inte tillgängligt på den här appen." meddelandet visas istället för videon som begärs
diff --git a/fastlane/metadata/android/sv/changelogs/989.txt b/fastlane/metadata/android/sv/changelogs/989.txt
new file mode 100644
index 000000000..28dcdf245
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/989.txt
@@ -0,0 +1,3 @@
+• [YouTube] Fixade oändlig laddning vid försök att spela någon video
+• [YouTube] Fixade throttling på vissa videor
+• Uppgraderade jsoup-biblioteket till 1.15.3, vilket inkluderar en säkerhetsfix
diff --git a/fastlane/metadata/android/sv/changelogs/990.txt b/fastlane/metadata/android/sv/changelogs/990.txt
new file mode 100644
index 000000000..1f1069acc
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/990.txt
@@ -0,0 +1,15 @@
+Denna version avslutar stödet för Android 4.4 KitKat, nu är den minsta versionen Android 5 Lollipop!
+
+Nytt
+• Ladda ner från lång-tryck menyn
+• Dölj framtida videor i flödet
+• Dela lokala spellistor
+
+Förbättrat
+• Refaktorera spelarkoden i små komponenter: mindre RAM används, mindre buggar
+• Förbättra miniatyrernas skalläge
+• Vectorisera bildplatshållare
+
+Fixat
+• Fixade problem med spelarens meddelande: föråldrad/saknad media info, förvrängd miniatyr
+• Fixade fullskärm som använde 1/4 av skärmen
diff --git a/fastlane/metadata/android/sv/changelogs/991.txt b/fastlane/metadata/android/sv/changelogs/991.txt
new file mode 100644
index 000000000..7f79c5ee7
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/991.txt
@@ -0,0 +1,13 @@
+Nytt
+• Lägg till "Öppna i webbläsare" knappen i felpanel
+• Lägg till inställning för att visa kanalgrupper som lista
+• [YouTube] Långtryck på strömsegment för att dela tidsstämpel URL
+• Lägg till play kö knapp till minispelare
+
+Förbättrat
+• Lägg till isländsk lokalisering och uppdatera många andra översättningar
+• Många interna förbättringar
+
+Fixat
+• Fixade flera kraschar
+• [YouTube] Fixade laddning av kanaler, icke-dedikerade flöden och uppspelningsproblem i vissa länder
diff --git a/fastlane/metadata/android/sv/changelogs/992.txt b/fastlane/metadata/android/sv/changelogs/992.txt
new file mode 100644
index 000000000..253f1e531
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/992.txt
@@ -0,0 +1,17 @@
+Nytt
+• Prenumerant antal i videodetaljer
+• Ladda ner från kön
+• Ställ in en permanent miniatyr fr spellista
+• Långtryckt hashtags och länkar
+• Kortvys läge
+
+Förbättrat
+• Större minispelar stäng knapp
+• Mjukare miniatyrs nedskalning
+• Sikta på Android 13 (API 33)
+• Sökning pausar inte längre spelaren
+
+Fixat
+• Fixade overlay på DeX/Mouse
+• Tillåt bakgrundsspelare utan separata ljudströmmar
+• Olika YouTube-fixar och mer…
diff --git a/fastlane/metadata/android/sv/changelogs/993.txt b/fastlane/metadata/android/sv/changelogs/993.txt
new file mode 100644
index 000000000..a06eafcd7
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/993.txt
@@ -0,0 +1,12 @@
+Nytt
+• La till varning vid tilläggning av dubbletter i spellista och la till knapp för att ta bort dem
+• Tillåt ignorering av hårdvaruknappar
+• Tillåt att dölja delvis tittade videor från flödet
+
+Förbättrat
+• Använd fler rutnät kolumner på stora skärmar
+• Gör framstegsindikatorer konsekventa med inställningar
+
+Fixat
+• Fixade öppning av webbläsar URLs, nedladdningar och externa spelare på Android 11+
+• Fixade interaktion med helskärm som kräver två tryck på MIUI
diff --git a/fastlane/metadata/android/sv/changelogs/994.txt b/fastlane/metadata/android/sv/changelogs/994.txt
new file mode 100644
index 000000000..a07e83500
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/994.txt
@@ -0,0 +1,15 @@
+Nytt
+• Stöd flera ljudspår/språk
+• Tillåt inställning av volym och ljusstyrks gester på valfri sida av skärmen
+• Stöd för att visa huvudflikar längst ner på skärmen
+
+Förbättrat
+• [Bandcamp] Hantera spår bakom betalvägg
+
+Fixat
+• [YouTube] 403 HTTP-fel för strömmar
+• Svart spelare vid byte till huvudspelare från spellistans vy
+• Spela service minnesläckor
+• [PeerTube] Uppladdare och sub kanal avatarer var utbytta
+
+och mer
diff --git a/fastlane/metadata/android/sv/changelogs/995.txt b/fastlane/metadata/android/sv/changelogs/995.txt
new file mode 100644
index 000000000..be1460739
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/995.txt
@@ -0,0 +1,16 @@
+Nytt
+• Stöd kanalflikar
+• Välj bildkvalitet
+• Få webbadresser till alla bilder
+
+Förbättrat
+• Tillgänglighet för spelargränssnitt
+• Bättre ljudval för endast video nedladdningar
+• Alternativ att inkludera spellista och videonamn till delat spellistinnehåll
+
+Fixat
+• [YouTube] Fixade hämtning av gillaantal
+• Fixade spelare som inte svarar popup och kraschar
+• Val av fel språk i språkväljare
+• Spelarens ljudfokus respekterade inte mute
+• Spellist-objekt addition som ibland inte fungerade
diff --git a/fastlane/metadata/android/sv/changelogs/996.txt b/fastlane/metadata/android/sv/changelogs/996.txt
new file mode 100644
index 000000000..8ee87d8c8
--- /dev/null
+++ b/fastlane/metadata/android/sv/changelogs/996.txt
@@ -0,0 +1,2 @@
+Fixade en NullPointerException när du öppnar en kanal / konferens i media.ccc.de.
+Grinchen försökte förstöra vår julklapp till er, men vi fixade den.
diff --git a/fastlane/metadata/android/sv/short_description.txt b/fastlane/metadata/android/sv/short_description.txt
index 6b29f892c..d0e04f585 100644
--- a/fastlane/metadata/android/sv/short_description.txt
+++ b/fastlane/metadata/android/sv/short_description.txt
@@ -1 +1 @@
-En gratis lättviktsklient för YouTube på Android.
+En gratis lättviktsklient för YouTube för Android.
diff --git a/fastlane/metadata/android/tr/full_description.txt b/fastlane/metadata/android/tr/full_description.txt
index 9422ff2ba..2b31f8dfd 100644
--- a/fastlane/metadata/android/tr/full_description.txt
+++ b/fastlane/metadata/android/tr/full_description.txt
@@ -1,2 +1,2 @@
-NewPipe herhangi bir Google frameworkü veya YouTube API'si kullanmaz. Sadece, ihtiyaç duyduğu bilgiyi edinmek için web sitesini ayrıştırır.
-Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca, NewPipe'ı kullanırken YouTube hesabına ihtiyacınız yok, ve bu özgür ve açık kaynaklı bir yazılımdır.
+NewPipe herhangi bir Google framework’ü ya da YouTube API’ı kullanmaz. Yalnızca, kendisine gereken bilgiyi edinmek için web sitesini ayrıştırır.
+Bu nedenle Google hizmetlerinin kurulmadığı aygıtlarda kullanılabilir. Ayrıca NewPipe'ı kullanırken bir YouTube hesabına gerek yoktur ve NewPipe özgür ve açık kaynaklı bir yazılımdır.
diff --git a/fastlane/metadata/android/uk/changelogs/996.txt b/fastlane/metadata/android/uk/changelogs/996.txt
new file mode 100644
index 000000000..e9fd2c523
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/996.txt
@@ -0,0 +1,2 @@
+Виправлено NullPointerException під час відкриття каналу / конференції у media.ccc.de.
+Грінч намагався зламати наш різдвяний подарунок, але ми його полагодили.
diff --git a/fastlane/metadata/android/vi/changelogs/996.txt b/fastlane/metadata/android/vi/changelogs/996.txt
new file mode 100644
index 000000000..2f92c6dc2
--- /dev/null
+++ b/fastlane/metadata/android/vi/changelogs/996.txt
@@ -0,0 +1,2 @@
+Đã sửa lỗi NullPointerException khi mở kênh/hội nghị trong media.ccc.de.
+Grinch đã cố làm vỡ món quà Giáng sinh của chúng tôi dành cho bạn, nhưng chúng tôi đã sửa nó.
diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/995.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/995.txt
index ddd775f08..87050f4a5 100644
--- a/fastlane/metadata/android/zh_Hant_HK/changelogs/995.txt
+++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/995.txt
@@ -1,14 +1,16 @@
 新嘢
 • 支援頻道分頁
 • 啲圖有得揀畫質
-• 啲圖全部都有得攞返個 URL
+• 啲圖全部都有得攞返條拎
 
 進步
 • 播放器介面暢易達
-• 分享播放清單有得加埋播放清單個名同入面啲片名
-• 內部提昇以及依賴元件更新
+• 揀返條靚啲嘅聲黐返落去齋晝面嘅影片下載
+• 分享播放清單時有得加埋播放清單個名同入面啲片名
 
 修正
+• [YouTube] 執返掂個 like 數
+• 執返好播放器話冇回應個框框同埋閃退
 • 揀選版面語言揀錯文
 • 播放器聲音焦點無視噤聲
 • 加入項目去播放清單有時唔得
diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/996.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/996.txt
new file mode 100644
index 000000000..8b136fc42
--- /dev/null
+++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/996.txt
@@ -0,0 +1,2 @@
+修正開啟 media.ccc.de 嘅頻道/會議時發生 NullPointerException 失卻指標錯誤。
+柳煙輕愜夜,俊緒留江影。謝謝您陪我哋跑完悲歡離合嘅一年。

From bec18e13d3a82f6b946d6bced353dd97178743c9 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <isirasen96@gmail.com>
Date: Mon, 27 Nov 2023 09:08:53 +0530
Subject: [PATCH 095/141] Improve app signature check

---
 .../schabi/newpipe/util/ReleaseVersionUtil.kt | 88 ++++---------------
 1 file changed, 15 insertions(+), 73 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
index 5a54b29d2..fc31a4d94 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
+++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
@@ -1,97 +1,39 @@
 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.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"
+    private const val RELEASE_CERT_PUBLIC_KEY_SHA256 =
+        "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab"
 
     @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 cf = CertificateFactory.getInstance("X509")
-                cf.generateCertificate(signatures[0].toByteArray().inputStream()) 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)
+        @Suppress("NewApi")
+        val certificates = mapOf(
+            RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256
         )
+        val app = App.getApp()
+        return try {
+            PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false)
+        } catch (e: PackageManager.NameNotFoundException) {
+            createNotification(
+                app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info")
+            )
+            false
+        }
     }
 
     fun isLastUpdateCheckExpired(expiry: Long): Boolean {
-        return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
+        return Instant.ofEpochSecond(expiry) < Instant.now()
     }
 
     /**

From 2e53a99361cdc9f380ccd7ace8dd21ed8aa321f4 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <isirasen96@gmail.com>
Date: Tue, 6 Feb 2024 05:11:13 +0530
Subject: [PATCH 096/141] Convert isReleaseApk to lazy value

---
 .../main/java/org/schabi/newpipe/NewVersionWorker.kt   | 10 ++++------
 .../schabi/newpipe/settings/MainSettingsFragment.java  |  2 +-
 .../org/schabi/newpipe/settings/SettingsActivity.java  |  2 +-
 .../java/org/schabi/newpipe/util/ReleaseVersionUtil.kt |  5 ++---
 4 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
index 39d8e90dc..000b83953 100644
--- a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
+++ b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
@@ -20,9 +20,7 @@ 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 org.schabi.newpipe.util.ReleaseVersionUtil
 import java.io.IOException
 
 class NewVersionWorker(
@@ -84,7 +82,7 @@ class NewVersionWorker(
     @Throws(IOException::class, ReCaptchaException::class)
     private fun checkNewVersion() {
         // Check if the current apk is a github one or not.
-        if (!isReleaseApk()) {
+        if (!ReleaseVersionUtil.isReleaseApk) {
             return
         }
 
@@ -93,7 +91,7 @@ class NewVersionWorker(
             // 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)) {
+            if (!ReleaseVersionUtil.isLastUpdateCheckExpired(expiry)) {
                 return
             }
         }
@@ -108,7 +106,7 @@ class NewVersionWorker(
         try {
             // Store a timestamp which needs to be exceeded,
             // before a new request to the API is made.
-            val newExpiry = coerceUpdateCheckExpiry(response.getHeader("expires"))
+            val newExpiry = ReleaseVersionUtil.coerceUpdateCheckExpiry(response.getHeader("expires"))
             prefs.edit {
                 putLong(applicationContext.getString(R.string.update_expiry_key), newExpiry)
             }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
index 3776d78f6..32e33d55b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
@@ -23,7 +23,7 @@ public class MainSettingsFragment extends BasePreferenceFragment {
         setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
 
         // Check if the app is updatable
-        if (!ReleaseVersionUtil.isReleaseApk()) {
+        if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
             getPreferenceScreen().removePreference(
                     findPreference(getString(R.string.update_pref_screen_key)));
 
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
index 3ee6668bf..529e53442 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -266,7 +266,7 @@ public class SettingsActivity extends AppCompatActivity implements
      */
     private void ensureSearchRepresentsApplicationState() {
         // Check if the update settings are available
-        if (!ReleaseVersionUtil.isReleaseApk()) {
+        if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
             SettingsResourceRegistry.getInstance()
                     .getEntryByPreferencesResId(R.xml.update_settings)
                     .setSearchable(false);
diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
index fc31a4d94..fc1a7d8cc 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
+++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
@@ -15,14 +15,13 @@ object ReleaseVersionUtil {
     private const val RELEASE_CERT_PUBLIC_KEY_SHA256 =
         "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab"
 
-    @JvmStatic
-    fun isReleaseApk(): Boolean {
+    val isReleaseApk by lazy {
         @Suppress("NewApi")
         val certificates = mapOf(
             RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256
         )
         val app = App.getApp()
-        return try {
+        try {
             PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false)
         } catch (e: PackageManager.NameNotFoundException) {
             createNotification(

From 5bdb6f18d60783b38043f547f0131104531e70e5 Mon Sep 17 00:00:00 2001
From: Isira Seneviratne <isirasen96@gmail.com>
Date: Wed, 20 Mar 2024 06:49:09 +0530
Subject: [PATCH 097/141] Use hexToByteArray() extension

---
 .../main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
index fc1a7d8cc..3ea19fa4f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
+++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
@@ -15,10 +15,11 @@ object ReleaseVersionUtil {
     private const val RELEASE_CERT_PUBLIC_KEY_SHA256 =
         "cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab"
 
+    @OptIn(ExperimentalStdlibApi::class)
     val isReleaseApk by lazy {
         @Suppress("NewApi")
         val certificates = mapOf(
-            RELEASE_CERT_PUBLIC_KEY_SHA256.toByteArray() to PackageManager.CERT_INPUT_SHA256
+            RELEASE_CERT_PUBLIC_KEY_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256
         )
         val app = App.getApp()
         try {

From 2e318b8b03db66a204f77b4a473cdf08ebfb82de Mon Sep 17 00:00:00 2001
From: CloudyRowly <76801836+CloudyRowly@users.noreply.github.com>
Date: Thu, 21 Mar 2024 19:18:55 +1100
Subject: [PATCH 098/141] Added "free memory" check before downloading [Android
 N / API 24+] (#10505)

Added "free space" check before downloading eliminating bugs related to out-of-memory on Android N / API 24+
---
 .../newpipe/download/DownloadDialog.java      | 23 +++++++++-
 .../streams/io/StoredDirectoryHelper.java     | 45 +++++++++++++++++++
 .../java/us/shandian/giga/util/Utility.java   | 18 +++++++-
 3 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index 1375d661e..fa312e55e 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -16,6 +16,7 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -147,7 +148,6 @@ public class DownloadDialog extends DialogFragment
             registerForActivityResult(
                     new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);
 
-
     /*//////////////////////////////////////////////////////////////////////////
     // Instance creation
     //////////////////////////////////////////////////////////////////////////*/
@@ -565,7 +565,6 @@ public class DownloadDialog extends DialogFragment
         }
     }
 
-
     /*//////////////////////////////////////////////////////////////////////////
     // Listeners
     //////////////////////////////////////////////////////////////////////////*/
@@ -784,6 +783,7 @@ public class DownloadDialog extends DialogFragment
         final StoredDirectoryHelper mainStorage;
         final MediaFormat format;
         final String selectedMediaType;
+        final long size;
 
         // first, build the filename and get the output folder (if possible)
         // later, run a very very very large file checking logic
@@ -795,6 +795,7 @@ public class DownloadDialog extends DialogFragment
                 selectedMediaType = getString(R.string.last_download_type_audio_key);
                 mainStorage = mainStorageAudio;
                 format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
+                size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
                 if (format == MediaFormat.WEBMA_OPUS) {
                     mimeTmp = "audio/ogg";
                     filenameTmp += "opus";
@@ -807,6 +808,7 @@ public class DownloadDialog extends DialogFragment
                 selectedMediaType = getString(R.string.last_download_type_video_key);
                 mainStorage = mainStorageVideo;
                 format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
+                size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
                 if (format != null) {
                     mimeTmp = format.mimeType;
                     filenameTmp += format.getSuffix();
@@ -816,6 +818,7 @@ public class DownloadDialog extends DialogFragment
                 selectedMediaType = getString(R.string.last_download_type_subtitle_key);
                 mainStorage = mainStorageVideo; // subtitle & video files go together
                 format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
+                size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
                 if (format != null) {
                     mimeTmp = format.mimeType;
                 }
@@ -871,6 +874,22 @@ public class DownloadDialog extends DialogFragment
             return;
         }
 
+        // Check for free memory space (for api 24 and up)
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+            final long freeSpace = mainStorage.getFreeMemory();
+            if (freeSpace <= size) {
+                Toast.makeText(context, getString(R.
+                        string.error_insufficient_storage), Toast.LENGTH_LONG).show();
+                // move the user to storage setting tab
+                final Intent storageSettingsIntent = new Intent(Settings.
+                        ACTION_INTERNAL_STORAGE_SETTINGS);
+                if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) {
+                    startActivity(storageSettingsIntent);
+                }
+                return;
+            }
+        }
+
         // check for existing file with the same name
         checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
                 mimeTmp);
diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
index 74fc74c76..0fe2e0408 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
@@ -5,11 +5,15 @@ import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.provider.DocumentsContract;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.documentfile.provider.DocumentFile;
 
 import org.schabi.newpipe.settings.NewPipeSettings;
@@ -23,6 +27,7 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -30,6 +35,8 @@ import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
 import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
 import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
 
+import us.shandian.giga.util.Utility;
+
 public class StoredDirectoryHelper {
     private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
     public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -168,6 +175,44 @@ public class StoredDirectoryHelper {
         return docTree == null;
     }
 
+    /**
+     * Get free memory of the storage partition (root of the directory).
+     * @return amount of free memory in the volume of current directory (bytes)
+     */
+    @RequiresApi(api = Build.VERSION_CODES.N)  // Necessary for `getStorageVolume()`
+    public long getFreeMemory() {
+        final Uri uri = getUri();
+        final StorageManager storageManager = (StorageManager) context.
+                getSystemService(Context.STORAGE_SERVICE);
+        final List<StorageVolume> volumes = storageManager.getStorageVolumes();
+
+        final String docId = DocumentsContract.getDocumentId(uri);
+        final String[] split = docId.split(":");
+        if (split.length > 0) {
+            final String volumeId = split[0];
+
+            for (final StorageVolume volume : volumes) {
+                // if the volume is an internal system volume
+                if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) {
+                    return Utility.getSystemFreeMemory();
+                }
+
+                // if the volume is a removable volume (normally an SD card)
+                if (volume.isRemovable() && !volume.isPrimary()) {
+                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+                        try {
+                            final String sdCardUUID = volume.getUuid();
+                            return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID));
+                        } catch (final Exception e) {
+                            // do nothing
+                        }
+                    }
+                }
+            }
+        }
+        return Long.MAX_VALUE;
+    }
+
     /**
      * Only using Java I/O. Creates the directory named by this abstract pathname, including any
      * necessary but nonexistent parent directories.
diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java
index 3cfa22bd9..c75269757 100644
--- a/app/src/main/java/us/shandian/giga/util/Utility.java
+++ b/app/src/main/java/us/shandian/giga/util/Utility.java
@@ -2,6 +2,8 @@ package us.shandian.giga.util;
 
 import android.content.Context;
 import android.os.Build;
+import android.os.Environment;
+import android.os.StatFs;
 import android.util.Log;
 
 import androidx.annotation.ColorInt;
@@ -26,10 +28,8 @@ import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.net.HttpURLConnection;
 import java.util.Locale;
-import java.util.Random;
 
 import okio.ByteString;
-import us.shandian.giga.get.DownloadMission;
 
 public class Utility {
 
@@ -40,6 +40,20 @@ public class Utility {
         UNKNOWN
     }
 
+    /**
+     * Get amount of free system's memory.
+     * @return free memory (bytes)
+     */
+    public static long getSystemFreeMemory() {
+        try {
+            final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
+            return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
+        } catch (final Exception e) {
+            // do nothing
+        }
+        return -1;
+    }
+
     public static String formatBytes(long bytes) {
         Locale locale = Locale.getDefault();
         if (bytes < 1024) {

From 206d1b6db4f9b60f652fd02ac16cad5727e75ffc Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Thu, 21 Mar 2024 11:56:10 +0100
Subject: [PATCH 099/141] Add separate message when download is rejected due to
 insufficient storage

---
 .../main/java/us/shandian/giga/ui/adapter/MissionAdapter.java  | 2 +-
 app/src/main/res/values-ar-rLY/strings.xml                     | 2 +-
 app/src/main/res/values-ar/strings.xml                         | 2 +-
 app/src/main/res/values-az/strings.xml                         | 2 +-
 app/src/main/res/values-b+ast/strings.xml                      | 2 +-
 app/src/main/res/values-b+uz+Latn/strings.xml                  | 2 +-
 app/src/main/res/values-be/strings.xml                         | 2 +-
 app/src/main/res/values-bg/strings.xml                         | 2 +-
 app/src/main/res/values-bn-rBD/strings.xml                     | 2 +-
 app/src/main/res/values-bn-rIN/strings.xml                     | 2 +-
 app/src/main/res/values-bn/strings.xml                         | 2 +-
 app/src/main/res/values-ca/strings.xml                         | 2 +-
 app/src/main/res/values-ckb/strings.xml                        | 2 +-
 app/src/main/res/values-cs/strings.xml                         | 2 +-
 app/src/main/res/values-da/strings.xml                         | 2 +-
 app/src/main/res/values-de/strings.xml                         | 2 +-
 app/src/main/res/values-el/strings.xml                         | 2 +-
 app/src/main/res/values-eo/strings.xml                         | 2 +-
 app/src/main/res/values-es/strings.xml                         | 2 +-
 app/src/main/res/values-et/strings.xml                         | 2 +-
 app/src/main/res/values-eu/strings.xml                         | 2 +-
 app/src/main/res/values-fa/strings.xml                         | 2 +-
 app/src/main/res/values-fi/strings.xml                         | 2 +-
 app/src/main/res/values-fr/strings.xml                         | 2 +-
 app/src/main/res/values-gl/strings.xml                         | 2 +-
 app/src/main/res/values-he/strings.xml                         | 2 +-
 app/src/main/res/values-hi/strings.xml                         | 2 +-
 app/src/main/res/values-hr/strings.xml                         | 2 +-
 app/src/main/res/values-hu/strings.xml                         | 2 +-
 app/src/main/res/values-in/strings.xml                         | 2 +-
 app/src/main/res/values-is/strings.xml                         | 2 +-
 app/src/main/res/values-it/strings.xml                         | 2 +-
 app/src/main/res/values-ja/strings.xml                         | 2 +-
 app/src/main/res/values-ka/strings.xml                         | 2 +-
 app/src/main/res/values-kmr/strings.xml                        | 2 +-
 app/src/main/res/values-ko/strings.xml                         | 2 +-
 app/src/main/res/values-ku/strings.xml                         | 2 +-
 app/src/main/res/values-lt/strings.xml                         | 2 +-
 app/src/main/res/values-lv/strings.xml                         | 2 +-
 app/src/main/res/values-mk/strings.xml                         | 2 +-
 app/src/main/res/values-ml/strings.xml                         | 2 +-
 app/src/main/res/values-nb-rNO/strings.xml                     | 2 +-
 app/src/main/res/values-ne/strings.xml                         | 2 +-
 app/src/main/res/values-nl-rBE/strings.xml                     | 2 +-
 app/src/main/res/values-nl/strings.xml                         | 2 +-
 app/src/main/res/values-nqo/strings.xml                        | 2 +-
 app/src/main/res/values-or/strings.xml                         | 2 +-
 app/src/main/res/values-pa/strings.xml                         | 2 +-
 app/src/main/res/values-pl/strings.xml                         | 2 +-
 app/src/main/res/values-pt-rBR/strings.xml                     | 2 +-
 app/src/main/res/values-pt-rPT/strings.xml                     | 2 +-
 app/src/main/res/values-pt/strings.xml                         | 2 +-
 app/src/main/res/values-ro/strings.xml                         | 2 +-
 app/src/main/res/values-ru/strings.xml                         | 2 +-
 app/src/main/res/values-ryu/strings.xml                        | 2 +-
 app/src/main/res/values-sat/strings.xml                        | 2 +-
 app/src/main/res/values-sc/strings.xml                         | 2 +-
 app/src/main/res/values-sk/strings.xml                         | 2 +-
 app/src/main/res/values-sl/strings.xml                         | 2 +-
 app/src/main/res/values-so/strings.xml                         | 2 +-
 app/src/main/res/values-sq/strings.xml                         | 2 +-
 app/src/main/res/values-sr/strings.xml                         | 2 +-
 app/src/main/res/values-sv/strings.xml                         | 2 +-
 app/src/main/res/values-tr/strings.xml                         | 2 +-
 app/src/main/res/values-uk/strings.xml                         | 2 +-
 app/src/main/res/values-ur/strings.xml                         | 2 +-
 app/src/main/res/values-vi/strings.xml                         | 2 +-
 app/src/main/res/values-zh-rCN/strings.xml                     | 2 +-
 app/src/main/res/values-zh-rHK/strings.xml                     | 2 +-
 app/src/main/res/values-zh-rTW/strings.xml                     | 2 +-
 app/src/main/res/values/strings.xml                            | 3 ++-
 71 files changed, 72 insertions(+), 71 deletions(-)

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 23f1bf6a7..31e7f663d 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
@@ -490,7 +490,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
                 showError(mission, UserAction.DOWNLOAD_POSTPROCESSING, R.string.error_postprocessing_failed);
                 return;
             case ERROR_INSUFFICIENT_STORAGE:
-                msg = R.string.error_insufficient_storage;
+                msg = R.string.error_insufficient_storage_left;
                 break;
             case ERROR_UNKNOWN_EXCEPTION:
                 if (mission.errObject != null) {
diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml
index aca74e743..c57ead36a 100644
--- a/app/src/main/res/values-ar-rLY/strings.xml
+++ b/app/src/main/res/values-ar-rLY/strings.xml
@@ -271,7 +271,7 @@
     <string name="import_data_summary">يلغي السجل الحالي والاشتراكات وقوائم التشغيل والإعدادات (اختياريًا)</string>
     <string name="app_ui_crash">تعطل التطبيق / واجهة المستخدم</string>
     <string name="rename">إعادة التسمية</string>
-    <string name="error_insufficient_storage">لم يتبقى مساحة في الجهاز</string>
+    <string name="error_insufficient_storage_left">لم يتبقى مساحة في الجهاز</string>
     <string name="could_not_setup_download_menu">تعذر إعداد قائمة التنزيل</string>
     <string name="download_path_dialog_title">اختر مجلد التنزيل لملفات الفيديو</string>
     <string name="notifications_disabled">تم تعطيل الإشعارات</string>
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index ce62198cd..d69564493 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -407,7 +407,7 @@
     <string name="overwrite_failed">لا يمكن الكتابة فوق الملف</string>
     <string name="download_already_pending">هناك تنزيل معلق بهذا الاسم</string>
     <string name="error_postprocessing_stopped">تم إغلاق NewPipe أثناء العمل على الملف</string>
-    <string name="error_insufficient_storage">لم يتبقى مساحة في الجهاز</string>
+    <string name="error_insufficient_storage_left">لم يتبقى مساحة في الجهاز</string>
     <string name="error_progress_lost">تم فقد التقدم بسبب حذف الملف</string>
     <string name="error_timeout">انتهى وقت الاتصال</string>
     <string name="confirm_prompt">هل تريد محو سجل التنزيل، أم تريد حذف جميع الملفات التي تم تنزيلها؟</string>
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index da08069ac..c0ac7e6d5 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -548,7 +548,7 @@
     <string name="remove_watched">İzləniləni sil</string>
     <string name="downloads_storage_use_saf_title">Sistem qovluğu seçicisini (SAF) istifadə et</string>
     <string name="error_timeout">Bağlantı fasiləsi</string>
-    <string name="error_insufficient_storage">Cihazda yer qalmayıb</string>
+    <string name="error_insufficient_storage_left">Cihazda yer qalmayıb</string>
     <string name="error_postprocessing_stopped">Fayl üzərində işləyərkən NewPipe bağlandı</string>
     <string name="error_postprocessing_failed">Emaldan sonra uğursuz oldu</string>
     <string name="error_connect_host">Serverə qoşulmaq mümkün deyil</string>
diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml
index 626e3f284..5cc516f46 100644
--- a/app/src/main/res/values-b+ast/strings.xml
+++ b/app/src/main/res/values-b+ast/strings.xml
@@ -171,7 +171,7 @@
     <string name="overwrite_finished_warning">Yá esiste un ficheru baxáu con esti nome</string>
     <string name="overwrite_failed">nun pue sobrecribise\'l ficheru</string>
     <string name="download_already_pending">Hai una descarga pendiente con esti nome</string>
-    <string name="error_insufficient_storage">Nun queda espaciu nel preséu</string>
+    <string name="error_insufficient_storage_left">Nun queda espaciu nel preséu</string>
     <string name="error_timeout">Escosó\'l tiempu d\'espera de la conexón</string>
     <string name="subscriptions_import_unsuccessful">Nun pudieron importase les soscripciones</string>
     <string name="caption_setting_title">Sotítulos</string>
diff --git a/app/src/main/res/values-b+uz+Latn/strings.xml b/app/src/main/res/values-b+uz+Latn/strings.xml
index 3bd940d11..c3c187891 100644
--- a/app/src/main/res/values-b+uz+Latn/strings.xml
+++ b/app/src/main/res/values-b+uz+Latn/strings.xml
@@ -416,7 +416,7 @@
     <string name="error_download_resource_gone">Ushbu yuklab olishni tiklab bo\'lmaydi</string>
     <string name="error_timeout">Ulanish vaqti tugadi</string>
     <string name="error_progress_lost">Siljish yo\'qoldi, chunki fayl o\'chirildi</string>
-    <string name="error_insufficient_storage">Qurilmada bo\'sh joy qolmadi</string>
+    <string name="error_insufficient_storage_left">Qurilmada bo\'sh joy qolmadi</string>
     <string name="error_postprocessing_stopped">NewPipe fayl ustida ishlash paytida yopilgan</string>
     <string name="error_postprocessing_failed">Keyingi ishlov berilmadi</string>
     <string name="error_http_not_found">Topilmadi</string>
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index ed24aac81..c41a9e6bb 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -399,7 +399,7 @@
     <string name="overwrite_failed">немагчыма перазапісаць файл</string>
     <string name="download_already_pending">Ў чарзе ўжо ёсць загрузка з такім імем</string>
     <string name="error_postprocessing_stopped">NewPipe была зачынена падчас працы над файлам</string>
-    <string name="error_insufficient_storage">Скончылася вольнае месца на прыладзе</string>
+    <string name="error_insufficient_storage_left">Скончылася вольнае месца на прыладзе</string>
     <string name="error_progress_lost">Прагрэс страчаны, так як файл быў выдалены</string>
     <string name="error_timeout">Час злучэння выйшла</string>
     <string name="confirm_prompt">Вы ўпэўнены\?</string>
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index fc845fdf9..78dcd0a93 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -467,7 +467,7 @@
     <string name="choose_instance_prompt">Изберете инстанция</string>
     <string name="comments_are_disabled">Коментарите са изключени</string>
     <string name="main_page_content_summary">Кои раздели се показват на началната страница</string>
-    <string name="error_insufficient_storage">Няма свободно място на устройството</string>
+    <string name="error_insufficient_storage_left">Няма свободно място на устройството</string>
     <plurals name="seconds">
         <item quantity="one">%d секунда</item>
         <item quantity="other">%d секунди</item>
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 4f27d60ce..7ac77e353 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -165,7 +165,7 @@
     <string name="stop">বন্ধ করুন</string>
     <string name="delete_downloaded_files">ডাউনলোড করা ফাইলগুলো ডিলিট করুন</string>
     <string name="clear_download_history">ডাওন লোড ইতিহাস মুছুন</string>
-    <string name="error_insufficient_storage">ডিভাইস এ স্পেস নেই</string>
+    <string name="error_insufficient_storage_left">ডিভাইস এ স্পেস নেই</string>
     <string name="error_http_not_found">পাওয়া যায় নি</string>
     <string name="error_unknown_host">সার্ভার পাওয়া যায় নি</string>
     <string name="show_error">এরর দেখান</string>
diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml
index cb5c0e595..29522f6ad 100644
--- a/app/src/main/res/values-bn-rIN/strings.xml
+++ b/app/src/main/res/values-bn-rIN/strings.xml
@@ -141,7 +141,7 @@
     <string name="app_language_title">অ্যাপ এর ভাষা</string>
     <string name="stop">বন্ধ করুন</string>
     <string name="clear_download_history">ডাওন লোড ইতিহাস মুছুন</string>
-    <string name="error_insufficient_storage">ডিভাইস এ স্পেস নেই</string>
+    <string name="error_insufficient_storage_left">ডিভাইস এ স্পেস নেই</string>
     <string name="error_http_not_found">পাওয়া যায় নি</string>
     <string name="error_unknown_host">সার্ভার পাওয়া যায় নি</string>
     <string name="download_failed">ডাউন লোড হয় নি</string>
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index 13bda43ae..c0851f9d6 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -10,7 +10,7 @@
     <string name="stop">বন্ধ করুন</string>
     <string name="delete_downloaded_files">ডাউনলোড করা ফাইলগুলো ডিলিট করুন</string>
     <string name="clear_download_history">ডাওন লোড ইতিহাস মুছুন</string>
-    <string name="error_insufficient_storage">ডিভাইস এ স্পেস নেই</string>
+    <string name="error_insufficient_storage_left">ডিভাইস এ স্পেস নেই</string>
     <string name="error_http_not_found">পাওয়া যায় নি</string>
     <string name="error_unknown_host">সার্ভার পাওয়া যায় নি</string>
     <string name="show_error">এরর দেখান</string>
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 6f4724f3b..19a74923f 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -385,7 +385,7 @@
     <string name="enable_playback_resume_title">Reprèn la reproducció</string>
     <string name="overwrite_failed">No es pot sobreescriure el fitxer</string>
     <string name="download_already_pending">Hi ha una baixada pendent amb aquest nom</string>
-    <string name="error_insufficient_storage">No hi ha espai disponible al dispositiu</string>
+    <string name="error_insufficient_storage_left">No hi ha espai disponible al dispositiu</string>
     <string name="error_progress_lost">S\'ha perdut el progrés perquè s\'ha eliminat el fitxer</string>
     <string name="error_timeout">S\'ha excedit el temps d\'espera de la connexió</string>
     <string name="confirm_prompt">Esteu segurs que voleu esborrar el vostre historial de baixades o esborrar-ne tots els fitxers\?</string>
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index 3cfa5ef7b..7c60b33c0 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -21,7 +21,7 @@
     <string name="grid">هێڵەکی</string>
     <string name="auto_queue_summary">به‌رده‌وامبوون له‌ (به‌بێ دووباره‌كردنه‌وه‌) نۆبه‌تی کارپێکەر به‌پێی په‌خشی هاوشێوه‌</string>
     <string name="enable_queue_limit">سنووردانانی نۆرەی دابەزاندن</string>
-    <string name="error_insufficient_storage">بیرگەی ناوەکیت پڕ بووە</string>
+    <string name="error_insufficient_storage_left">بیرگەی ناوەکیت پڕ بووە</string>
     <string name="subscribers_count_not_available">ژمارەی بەژداری نادیارە</string>
     <string name="overwrite_failed">ناتوانرێت لەسەر ئەو فایله‌وه‌ جێگیر بکرێت</string>
     <string name="tab_choose">په‌ڕه‌ هەڵبژێرە</string>
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 1a5b83e52..02f6486c9 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -396,7 +396,7 @@
     <string name="overwrite_failed">soubor nelze přepsat</string>
     <string name="download_already_pending">Soubor s tímto názvem již čeká na stažení</string>
     <string name="error_postprocessing_stopped">NewPipe byl ukončen v průběhu zpracovávání souboru</string>
-    <string name="error_insufficient_storage">V zařízení nezbývá žádné místo</string>
+    <string name="error_insufficient_storage_left">V zařízení nezbývá žádné místo</string>
     <string name="error_progress_lost">Postup ztracen, protože soubor byl smazán</string>
     <string name="confirm_prompt">Jste si jisti smazáním své historie stahování nebo smazáním všech stažených souborů\?</string>
     <string name="enable_queue_limit">Omezit frontu stahování</string>
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index f0ffba311..c3842e3b8 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -633,7 +633,7 @@
     <string name="drawer_header_description">Skift service, nuværende valg:</string>
     <string name="comments_are_disabled">Kommentarer er slået fra</string>
     <string name="no_app_to_open_intent">Ingen apps på din enhed kan åbne dette</string>
-    <string name="error_insufficient_storage">Ingen ledig plads på enheden</string>
+    <string name="error_insufficient_storage_left">Ingen ledig plads på enheden</string>
     <string name="app_language_title">App sprog</string>
     <string name="remove_watched_popup_yes_and_partially_watched_videos">Ja, og delvist sete videoer</string>
     <string name="feed_load_error">Fejl ved indlæsning af feed</string>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7fd1d4fed..c7506c649 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -405,7 +405,7 @@
     <string name="overwrite_failed">Datei kann nicht überschrieben werden</string>
     <string name="download_already_pending">Es ist ein ausstehender Download mit diesem Namen vorhanden</string>
     <string name="error_postprocessing_stopped">NewPipe wurde während der Verarbeitung der Datei geschlossen</string>
-    <string name="error_insufficient_storage">Kein Speicherplatz mehr auf dem Gerät</string>
+    <string name="error_insufficient_storage_left">Kein Speicherplatz mehr auf dem Gerät</string>
     <string name="error_progress_lost">Vorgang abgebrochen, da die Datei gelöscht wurde</string>
     <string name="confirm_prompt">Möchtest du deinen Downloadverlauf oder alle heruntergeladenen Dateien löschen\?</string>
     <string name="enable_queue_limit">Downloadwarteschlange begrenzen</string>
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 881f91b78..18ab48318 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -395,7 +395,7 @@
     <string name="overwrite_failed">δεν είναι δυνατή η αντικατάσταση του αρχείου</string>
     <string name="download_already_pending">Υπάρχει μια εκκρεμής λήψη με αυτό το όνομα</string>
     <string name="error_postprocessing_stopped">Το NewPipe τερματίστηκε ενώ επεξεργάζονταν το αρχείο</string>
-    <string name="error_insufficient_storage">Δεν υπάρχει αρκετός χώρος στη συσκευή</string>
+    <string name="error_insufficient_storage_left">Δεν υπάρχει αρκετός χώρος στη συσκευή</string>
     <string name="error_progress_lost">Η πρόοδος χάθηκε, επειδή το αρχείο διαγράφηκε</string>
     <string name="error_timeout">Λήξη χρονικού ορίου σύνδεσης</string>
     <string name="confirm_prompt">Θέλετε να διαγράψετε το ιστορικό λήψεων σας ή να διαγράψετε όλα τα αρχεία που έχετε λάβει;</string>
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 90f5fd2ec..3dc932388 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -406,7 +406,7 @@
     <string name="overwrite_finished_warning">Elŝutita dosieron kun ĉi tiu nomo jam ekzistas</string>
     <string name="download_already_pending">Estas pritraktata elŝuto kun ĉi tiu nomo</string>
     <string name="error_postprocessing_stopped">NewPipe estis fermita dum laborante sur la dosiero</string>
-    <string name="error_insufficient_storage">Neniu spaco havebla sur la aparato</string>
+    <string name="error_insufficient_storage_left">Neniu spaco havebla sur la aparato</string>
     <string name="error_progress_lost">Progreso perdita, ĉar la dosiero estis forviŝita</string>
     <string name="error_timeout">Eltempiĝo de Konekto</string>
     <string name="drawer_header_description">Ŝangi la servon, nuntempe elektita:</string>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 2da61b8d5..b17c4d169 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -376,7 +376,7 @@
     <string name="error_http_not_found">No encontrado</string>
     <string name="error_postprocessing_failed">Falló el posprocesamiento</string>
     <string name="error_postprocessing_stopped">NewPipe se cerró mientras se trabajaba en el archivo</string>
-    <string name="error_insufficient_storage">No hay suficiente espacio disponible en el dispositivo</string>
+    <string name="error_insufficient_storage_left">No hay suficiente espacio disponible en el dispositivo</string>
     <string name="error_progress_lost">Progreso perdido, porque el archivo fue borrado</string>
     <string name="error_timeout">El tiempo de conexión expiro</string>
     <string name="error_download_resource_gone">No se puede recuperar esta descarga</string>
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 1a9c5daac..1234aec5e 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -505,7 +505,7 @@
     <string name="clear_download_history">Kustuta allalaadimiste ajalugu</string>
     <string name="error_download_resource_gone">Seda allalaadimist ei saa uuesti alustada</string>
     <string name="error_timeout">Ühendus aegus</string>
-    <string name="error_insufficient_storage">Seadmes pole enam ruumi</string>
+    <string name="error_insufficient_storage_left">Seadmes pole enam ruumi</string>
     <string name="download_already_pending">Sellise nimega allalaadimine on juba pooleli</string>
     <string name="overwrite_failed">faili asendamine ei õnnestu</string>
     <string name="feed_create_new_group_button_title">Uus</string>
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 69a42d1c2..3c36a85ae 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -396,7 +396,7 @@
     <string name="overwrite_failed">Ezin da fitxategia gainidatzi</string>
     <string name="download_already_pending">Badago izen bereko deskarga bat burutzeke</string>
     <string name="error_postprocessing_stopped">NewPipe itxi egin da fitxategian lanean zegoela</string>
-    <string name="error_insufficient_storage">Ez dago lekurik gailuan</string>
+    <string name="error_insufficient_storage_left">Ez dago lekurik gailuan</string>
     <string name="error_progress_lost">Progresioa galdu da, fitxategia ezabatu delako</string>
     <string name="confirm_prompt">Zure deskargen historiala garbitu nahi duzu ala deskargatutako fitxategi guztiak ezabatu\?</string>
     <string name="enable_queue_limit">Mugatu deskargen ilara</string>
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 0dc26070a..01d26857e 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -384,7 +384,7 @@
     <string name="overwrite_failed">ناتوانی در بازنویسی پرونده</string>
     <string name="download_already_pending">یک بارگیری دیگر با همین نام در صف قرار دارد</string>
     <string name="error_postprocessing_stopped">نیوپایپ در خلال کار روی پرونده، بسته شد</string>
-    <string name="error_insufficient_storage">فضایی روی دستگاه باقی نمانده است</string>
+    <string name="error_insufficient_storage_left">فضایی روی دستگاه باقی نمانده است</string>
     <string name="error_progress_lost">پیشرفت کار متوفق شد زیرا پرونده پاک شده است</string>
     <string name="error_timeout">پایان زمان اتصال</string>
     <string name="confirm_prompt">می‌خواهید تاریخچه بارگیری را پاک کنید یا همه پرونده‌هایی که بارگیری شده‌اند؟</string>
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index a8ee1093e..c27115689 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -481,7 +481,7 @@
     <string name="error_download_resource_gone">Tätä latausta ei voi palauttaa</string>
     <string name="error_timeout">Yhteys aikakatkaistiin</string>
     <string name="error_progress_lost">Eteneminen menetettiin, koska tiedosto poistettiin</string>
-    <string name="error_insufficient_storage">Laitteella ei ole tilaa</string>
+    <string name="error_insufficient_storage_left">Laitteella ei ole tilaa</string>
     <string name="error_postprocessing_stopped">NewPipe suljettiin, kun se käsitteli tiedostoa</string>
     <string name="error_postprocessing_failed">Jälkikäsittely epäonnistui</string>
     <string name="error_http_not_found">Ei löytynyt</string>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 81ab11b88..51b572dce 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -395,7 +395,7 @@
     <string name="overwrite_failed">impossible d’écraser le fichier</string>
     <string name="download_already_pending">Il y a un téléchargement en attente avec ce nom</string>
     <string name="error_postprocessing_stopped">NewPipe a été fermé alors qu’il travaillait sur le fichier</string>
-    <string name="error_insufficient_storage">Aucun espace disponible sur l’appareil</string>
+    <string name="error_insufficient_storage_left">Aucun espace disponible sur l’appareil</string>
     <string name="error_progress_lost">Progression perdue car le fichier a été supprimé</string>
     <string name="confirm_prompt">Voulez-vous effacer l’historique de téléchargement ou supprimer tous les fichiers téléchargés \?</string>
     <string name="enable_queue_limit">Limiter la file d’attente de téléchargement</string>
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index bfc8aa6e1..93a43ddd2 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -419,7 +419,7 @@
     <string name="error_download_resource_gone">Non se pode recuperar esta descarga</string>
     <string name="error_timeout">O tempo de espera da conexión</string>
     <string name="error_progress_lost">Perdeuse o progreso porque se eliminou o ficheiro</string>
-    <string name="error_insufficient_storage">Non queda espazo no dispositivo</string>
+    <string name="error_insufficient_storage_left">Non queda espazo no dispositivo</string>
     <string name="error_postprocessing_stopped">NewPipe pechouse mentres se traballaba no ficheiro</string>
     <string name="error_postprocessing_failed">Fallou o post-procesamento</string>
     <string name="error_http_not_found">Non se atopou</string>
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index 5357e38fd..6cae41316 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -401,7 +401,7 @@
     <string name="overwrite_failed">לא ניתן לשכתב את הקובץ</string>
     <string name="download_already_pending">כבר יש הורדה ממתינה בשם הזה</string>
     <string name="error_postprocessing_stopped">NewPipe נסגר בזמן העבודה על הקובץ</string>
-    <string name="error_insufficient_storage">לא נשאר מקום במכשיר</string>
+    <string name="error_insufficient_storage_left">לא נשאר מקום במכשיר</string>
     <string name="error_progress_lost">התהליך אבד כיוון שהקובץ נמחק</string>
     <string name="error_timeout">החיבור המתין זמן רב מדי</string>
     <string name="confirm_prompt">למחוק את היסטוריית ההורדות שלך או למחוק את כל הקבצים שהורדת\?</string>
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index d155fab95..a425a9b04 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -379,7 +379,7 @@
     <string name="overwrite_failed">फाइल को ओवरराइट नहीं कर सकते</string>
     <string name="download_already_pending">इस नाम का एक डाउनलोड बाकी है</string>
     <string name="error_postprocessing_stopped">फ़ाइल पर कार्य करते समय न्यूपाइप बंद किया गया</string>
-    <string name="error_insufficient_storage">डिवाइस पर जगह समाप्त</string>
+    <string name="error_insufficient_storage_left">डिवाइस पर जगह समाप्त</string>
     <string name="error_progress_lost">प्रगति खो गई, क्योंकि फ़ाइल मिटा दी गई थी</string>
     <string name="error_timeout">कनेक्शन का समय समाप्त</string>
     <string name="confirm_prompt">क्या आप अपना डाउनलोड इतिहास मिटाना चाहते हैं या सभी डाउनलोड की गई फ़ाइलों को हटाना चाहते हैं\?</string>
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 330ae2d5c..8f077fdea 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -426,7 +426,7 @@
     <string name="feed_update_threshold_option_always_update">Uvijek aktualiziraj</string>
     <string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi način</string>
     <string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi način</string>
-    <string name="error_insufficient_storage">Memorija uređaja je popunjena</string>
+    <string name="error_insufficient_storage_left">Memorija uređaja je popunjena</string>
     <string name="most_liked">Najomiljeniji</string>
     <string name="subtitle_activity_recaptcha">Pritisni „Gotovo” kad je riješeno</string>
     <string name="done">Gotovo</string>
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 58428df59..13a488f07 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -348,7 +348,7 @@
     <string name="error_download_resource_gone">A letöltést nem lehet helyrehozni</string>
     <string name="error_timeout">Kapcsolati időtúllépés</string>
     <string name="error_progress_lost">Az előrehaladás elveszett, mert a fájlt törölték</string>
-    <string name="error_insufficient_storage">Nincs hely az eszközön</string>
+    <string name="error_insufficient_storage_left">Nincs hely az eszközön</string>
     <string name="error_postprocessing_stopped">A NewPipe leállt a fájl feldolgozása közben</string>
     <string name="error_postprocessing_failed">Utófeldolgozás sikertelen</string>
     <string name="error_http_not_found">Nincs talalat</string>
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 1bab98066..1ca35cfb8 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -389,7 +389,7 @@
     <string name="overwrite_failed">tidak bisa menimpa ulang berkas</string>
     <string name="download_already_pending">Ada unduhan yang dijeda dengan nama ini</string>
     <string name="error_postprocessing_stopped">NewPipe telah ditutup saat sedang memproses berkas</string>
-    <string name="error_insufficient_storage">Tidak ada ruang kosong tersisa pada perangkat</string>
+    <string name="error_insufficient_storage_left">Tidak ada ruang kosong tersisa pada perangkat</string>
     <string name="error_progress_lost">Kehilangan laju, karena berkas telah dihapus</string>
     <string name="confirm_prompt">Apakah Anda yakin ingin menghapus semua riwayat unduhan dan berkas yang telah diunduh\?</string>
     <string name="enable_queue_limit">Batasi antrean unduhan</string>
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index ee90a9333..c35d7a685 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -680,7 +680,7 @@
     <string name="error_http_no_content">Netþjónninn sendir ekki gögn</string>
     <string name="error_http_unsupported_range">Netþjónninn styður ekki fjölþráðuð niðurhöl, reyndu aftur með @string/msg_threads = 1</string>
     <string name="error_postprocessing_stopped">NewPipe var lokað á meðan unnið var að skrá</string>
-    <string name="error_insufficient_storage">Ekkert pláss eftir á tæki</string>
+    <string name="error_insufficient_storage_left">Ekkert pláss eftir á tæki</string>
     <string name="error_progress_lost">Framvinda tapaðist vegna þess að skránni var eytt</string>
     <string name="error_download_resource_gone">Get ekki endurheimt þetta niðurhal</string>
     <string name="clear_download_history">Hreinsa niðurhalsferil</string>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 17cdb1c4b..3d8162880 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -399,7 +399,7 @@
     <string name="overwrite_failed">impossibile sovrascrivere il file</string>
     <string name="download_already_pending">C\'è un download in corso con questo nome</string>
     <string name="error_postprocessing_stopped">NewPipe è stato chiuso mentre lavorava sul file</string>
-    <string name="error_insufficient_storage">Spazio insufficiente sul dispositivo</string>
+    <string name="error_insufficient_storage_left">Spazio insufficiente sul dispositivo</string>
     <string name="error_progress_lost">Progresso perso poiché il file è stato eliminato</string>
     <string name="confirm_prompt">Vuoi cancellare la cronologia dei download o eliminare tutti i file scaricati\?</string>
     <string name="enable_queue_limit_desc">Sarà avviato un solo download per volta</string>
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 93b52997c..03117abad 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -390,7 +390,7 @@
     <string name="overwrite_failed">ファイルを上書きできません</string>
     <string name="download_already_pending">同じファイル名のダウンロードが既に進行中です</string>
     <string name="error_postprocessing_stopped">ファイルの作業中に NewPipe が閉じられました</string>
-    <string name="error_insufficient_storage">デバイスに空き容量がありません</string>
+    <string name="error_insufficient_storage_left">デバイスに空き容量がありません</string>
     <string name="error_progress_lost">ファイルが削除されたため、進行状況が失われました</string>
     <string name="confirm_prompt">ダウンロード履歴、またはダウンロードしたファイルを消去しますか\?</string>
     <string name="enable_queue_limit">ダウンロード キューの制限</string>
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index 9f6227388..cbeb27d9d 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -461,7 +461,7 @@
     <string name="error_http_unsupported_range">სერვერი არ იღებს მრავალ ნაკადის ჩამოტვირთვებს, ხელახლა სცადეთ @string/msg_threads = 1</string>
     <string name="error_http_not_found">არ მოიძებნა</string>
     <string name="error_postprocessing_failed">შემდგომი დამუშავება ვერ მოხერხდა</string>
-    <string name="error_insufficient_storage">მოწყობილობაზე არ არის დარჩენილი თავისუფალი ადგილი</string>
+    <string name="error_insufficient_storage_left">მოწყობილობაზე არ არის დარჩენილი თავისუფალი ადგილი</string>
     <string name="error_timeout">Კავშირის დრო ამოიწურა</string>
     <string name="error_download_resource_gone">ამ ჩამოტვირთვის აღდგენა შეუძლებელია</string>
     <string name="delete_downloaded_files">გადმოწერილი ფაილების წაშლა</string>
diff --git a/app/src/main/res/values-kmr/strings.xml b/app/src/main/res/values-kmr/strings.xml
index aa4696c78..b55be410d 100644
--- a/app/src/main/res/values-kmr/strings.xml
+++ b/app/src/main/res/values-kmr/strings.xml
@@ -388,7 +388,7 @@
     <string name="error_download_resource_gone">Vê dakêşanê nikare paşde bibe</string>
     <string name="error_timeout">Dema girêdanê</string>
     <string name="error_progress_lost">Pêşkeftin winda bû, ji ber ku pel hate jêbirin</string>
-    <string name="error_insufficient_storage">Cih li cîhazê namîne</string>
+    <string name="error_insufficient_storage_left">Cih li cîhazê namîne</string>
     <string name="error_postprocessing_stopped">Dema ku pel dixebitî NewPipe hate girtin</string>
     <string name="error_postprocessing_failed">Pêvajoya şûnda têk çû</string>
     <string name="error_http_not_found">Peyda nebû</string>
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index e8651d966..d03be0920 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -389,7 +389,7 @@
     <string name="overwrite_failed">파일을 덮어쓰기할 수 없습니다</string>
     <string name="download_already_pending">해당 이름으로 대기된 다운로드가 있습니다</string>
     <string name="error_postprocessing_stopped">파일 작업 중에 Newpipe가 종료되었습니다</string>
-    <string name="error_insufficient_storage">남은 저장공간이 없습니다</string>
+    <string name="error_insufficient_storage_left">남은 저장공간이 없습니다</string>
     <string name="error_progress_lost">파일이 삭제되어 진행이 중지되었습니다</string>
     <string name="error_timeout">연결시간 초과</string>
     <string name="confirm_prompt">모든 다운로드 기록과 파일을 삭제합니다.확실합니까\?</string>
diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml
index 50ba6c205..cc59938b4 100644
--- a/app/src/main/res/values-ku/strings.xml
+++ b/app/src/main/res/values-ku/strings.xml
@@ -418,7 +418,7 @@
     <string name="pause_downloads_on_mobile_desc">بەسوودە بۆ کاتی گۆڕینی هێڵ بۆ داتای مۆبایل, لەگەڵ ئەوەشدا زۆربەی دابەزاندنەکان ڕاناگرێت</string>
     <string name="close">داخستن</string>
     <string name="error_postprocessing_stopped">ئەپ داخرا لەکاتی کارکردن لەسەر ئەو پەڕگەیە</string>
-    <string name="error_insufficient_storage">بیرگەی ناوەکیت پڕبووە</string>
+    <string name="error_insufficient_storage_left">بیرگەی ناوەکیت پڕبووە</string>
     <string name="error_progress_lost">کردارەکە شکستی هێنا, چونکە ئەو پەڕگەیە سڕاوەتەوە</string>
     <string name="error_timeout">هێڵی ئینتەرنێت نەما</string>
     <string name="confirm_prompt">ئایا دەتەوێ مێژووی دابەزاندنەکانت بسڕدرێنەوە یان هەموو فایلە دابەزێنراوەکانت بسڕدرێنەوە؟</string>
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 37345c38a..84699cf6d 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -553,7 +553,7 @@
     <string name="show_original_time_ago_title">Elementams rodyti orginalų \"prieš\" laiką</string>
     <string name="error_connect_host">Nepavyksta prisijungti prie serverio</string>
     <string name="error_file_creation">Failo sukurti nepavyko</string>
-    <string name="error_insufficient_storage">Įrenginyje nebėra vietos</string>
+    <string name="error_insufficient_storage_left">Įrenginyje nebėra vietos</string>
     <string name="error_unknown_host">Nepavyko rasti serverio</string>
     <string name="permission_denied">Sistema uždraudė veiksmą</string>
     <string name="feed_group_show_only_ungrouped_subscriptions">Rodyti tik negrupuotas prenumeratas</string>
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index 9bf252ccb..ab3f7b94c 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -371,7 +371,7 @@
     <string name="error_download_resource_gone">Nevar atgūt šo lejupielādi</string>
     <string name="error_timeout">Savienojums pārtraukts</string>
     <string name="error_progress_lost">Progress zaudēts, jo fails tika izdzēsts</string>
-    <string name="error_insufficient_storage">Ierīcē nav vietas</string>
+    <string name="error_insufficient_storage_left">Ierīcē nav vietas</string>
     <string name="error_postprocessing_stopped">Strādājot ar failu, NewPipe tika aizvērts</string>
     <string name="error_postprocessing_failed">Pēcapstrāde neizdevās</string>
     <string name="error_http_not_found">Nav atrasts</string>
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 03a285e37..8e03b26dd 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -379,7 +379,7 @@
     <string name="overwrite_unrelated_warning">Ддотека со ова име веќе постои</string>
     <string name="overwrite_finished_warning">Преземената дадотека со ова име веќе постои</string>
     <string name="error_postprocessing_stopped">NewPipe беше затворен додека работеше на датотеката</string>
-    <string name="error_insufficient_storage">Не останува простор на уредот</string>
+    <string name="error_insufficient_storage_left">Не останува простор на уредот</string>
     <string name="error_timeout">Истечено време за поврзување</string>
     <string name="confirm_prompt">Дали си сигурен\?</string>
     <string name="enable_queue_limit">Ограничи ја редицата за преземање</string>
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 38606fea8..c439593f7 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -494,7 +494,7 @@
     <string name="error_download_resource_gone">ഈ ഡൗൺലോഡ് വീണ്ടെടുക്കാനാവില്ല</string>
     <string name="error_timeout">കണക്ഷൻ കാലഹരണപ്പെട്ടു</string>
     <string name="error_progress_lost">ഫയൽ ഇല്ലാതാക്കിയതിനാൽ പുരോഗതി നഷ്‌ടപ്പെട്ടു</string>
-    <string name="error_insufficient_storage">ഉപകരണത്തിൽ ഇനിയൊരു സ്ഥലവും ബാക്കിയില്ല</string>
+    <string name="error_insufficient_storage_left">ഉപകരണത്തിൽ ഇനിയൊരു സ്ഥലവും ബാക്കിയില്ല</string>
     <string name="error_postprocessing_stopped">ഫയലിൽ പ്രവർത്തിക്കുമ്പോൾ ന്യൂപൈപ്പ് അടച്ചു</string>
     <string name="error_postprocessing_failed">പോസ്റ്റ്-പ്രോസസ്സിംഗ് പരാജയപ്പെട്ടു</string>
     <string name="error_http_not_found">കണ്ടെത്താനായില്ല</string>
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index c1cb3db93..6f232ba06 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -395,7 +395,7 @@
     <string name="overwrite_failed">Kan ikke overskrive filen</string>
     <string name="download_already_pending">Det finnes en ventende nedlasting ved dette navnet</string>
     <string name="error_postprocessing_stopped">NewPipe ble lukket under arbeid med filen</string>
-    <string name="error_insufficient_storage">Ingen ledig plass på enheten</string>
+    <string name="error_insufficient_storage_left">Ingen ledig plass på enheten</string>
     <string name="error_progress_lost">Framdrift gikk tapt, fordi filen ble slettet</string>
     <string name="error_timeout">Tilkoblingsavbrudd</string>
     <string name="confirm_prompt">Ønsker du å slette din nedlastingshistorikk eller slette alle nedlastede filer\?</string>
diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml
index b573bb79d..5e6c1eece 100644
--- a/app/src/main/res/values-ne/strings.xml
+++ b/app/src/main/res/values-ne/strings.xml
@@ -389,7 +389,7 @@
     <string name="overwrite_failed">फाइल अधिलेखन गर्न सकिएन</string>
     <string name="download_already_pending">यसै नाम सितको एक फाइल डाउनलोड हुने प्रक्रियामा छ</string>
     <string name="error_postprocessing_stopped">फाइल मा काम गर्दा NewPipe बन्द भएको थियो</string>
-    <string name="error_insufficient_storage">उपकरणमा कुनै ठाउँ बाकी छैन</string>
+    <string name="error_insufficient_storage_left">उपकरणमा कुनै ठाउँ बाकी छैन</string>
     <string name="error_progress_lost">प्रगति हरायो, किनभने फाइल मेटिएको थियो</string>
     <string name="error_timeout">जडान समय सकियो</string>
     <string name="confirm_prompt">तपाईं आफ्नो डाउनलोड इतिहास वा डाउनलोड फाइल मेटाउन चाहनुहुन्छ\?</string>
diff --git a/app/src/main/res/values-nl-rBE/strings.xml b/app/src/main/res/values-nl-rBE/strings.xml
index b8370a1cb..637eb1751 100644
--- a/app/src/main/res/values-nl-rBE/strings.xml
+++ b/app/src/main/res/values-nl-rBE/strings.xml
@@ -465,7 +465,7 @@
     <string name="clear_download_history">Download geschiedenis verwijderen</string>
     <string name="error_download_resource_gone">Kan deze download niet herstellen</string>
     <string name="error_timeout">Verbinding time-out</string>
-    <string name="error_insufficient_storage">Geen vrije ruimte meer op het apparaat</string>
+    <string name="error_insufficient_storage_left">Geen vrije ruimte meer op het apparaat</string>
     <string name="error_postprocessing_stopped">NewPipe werd gesloten terwijl het bezig was met het bestand</string>
     <string name="download_already_pending">Er staat al een download met deze naam in wacht</string>
     <string name="overwrite_failed">Kan bestand niet overschrijven</string>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index b08e20202..edb822366 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -396,7 +396,7 @@
     <string name="overwrite_unrelated_warning">Een bestand met dezelfde naam bestaat al</string>
     <string name="overwrite_failed">Kan bestand niet overschrijven</string>
     <string name="download_already_pending">Er is al een download met deze naam bezig</string>
-    <string name="error_insufficient_storage">Geen vrije ruimte meer op het apparaat</string>
+    <string name="error_insufficient_storage_left">Geen vrije ruimte meer op het apparaat</string>
     <string name="error_progress_lost">Voortgang verloren, omdat bestand was verwijderd</string>
     <string name="confirm_prompt">Wilt u de downloadgeschiedenis of alle gedownloade bestanden verwijderen\?</string>
     <string name="enable_queue_limit">Download­wachtrij limiteren</string>
diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml
index 060021846..6956d6b78 100644
--- a/app/src/main/res/values-nqo/strings.xml
+++ b/app/src/main/res/values-nqo/strings.xml
@@ -517,7 +517,7 @@
     <string name="error_http_not_found">ߊ߬ ߕߍ߫ ߡߊߛߐ߬ߘߐ߲߬</string>
     <string name="error_postprocessing_failed">ߞߐߝߍ߫-ߦߙߍߞߍߟߌ ߓߘߊ߫ ߗߌߙߏ߲߫</string>
     <string name="error_postprocessing_stopped">ߣߌߎߔߌߔ ߘߊߕߎ߲߯ ߘߊ߫ ߟߋ߬ ߞߵߊ߬ ߕߘߍ߬ ߊ߬ ߦߋ߫ ߓߊ߯ߙߊ߫ ߟߊ߫ ߞߐߕߐ߮ ߞߊ߲߬</string>
-    <string name="error_insufficient_storage">ߜߍߞߣߍ߫ ߛߌ߫ ߕߍ߫ ߕߙߏߞߏ ߞߊ߲߬</string>
+    <string name="error_insufficient_storage_left">ߜߍߞߣߍ߫ ߛߌ߫ ߕߍ߫ ߕߙߏߞߏ ߞߊ߲߬</string>
     <string name="error_progress_lost">ߢߍߕߊ߮ ߓߘߊ߫ ߝߏ߫߸ ߓߊߏ߬ ߞߐߕߐ߮ ߓߘߊ߫ ߖߏ߬ߛߌ߫</string>
     <string name="error_download_resource_gone">ߟߊ߬ߖߌ߰ߟߌ ߣߌ߲߬ ߕߍ߫ ߛߋ߫ ߟߊߛߊ߬ߦߌ߬ ߟߊ߫</string>
     <string name="delete_downloaded_files">ߞߊ߬ ߞߐߕߐ߯ ߟߊߖߌ߰ߣߍ߲ ߠߎ߬ ߖߏ߬ߛߌ߫</string>
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 89715cd84..331c3248c 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -378,7 +378,7 @@
     <string name="error_progress_lost">ପ୍ରଗତି ହଜିଗଲା, କାରଣ ଫାଇଲ୍ ଡିଲିଟ୍ ହେଲା</string>
     <string name="error_postprocessing_failed">ପରବର୍ତ୍ତୀ ପ୍ରକ୍ରିୟାକରଣ ବିଫଳ ହେଲା</string>
     <string name="error_postprocessing_stopped">ଫାଇଲରେ କାମ କରିବାବେଳେ ନ୍ୟୁ ପାଇପ୍ ବନ୍ଦ ହୋଇଯାଇଥିଲା</string>
-    <string name="error_insufficient_storage">ଡିଭାଇସରେ କୌଣସି ସ୍ଥାନ ବାକି ନାହିଁ</string>
+    <string name="error_insufficient_storage_left">ଡିଭାଇସରେ କୌଣସି ସ୍ଥାନ ବାକି ନାହିଁ</string>
     <string name="delete_downloaded_files_confirm">ଡିସ୍କରୁ ସମସ୍ତ ଡାଉନଲୋଡ୍ ହୋଇଥିବା ଫାଇଲଗୁଡ଼ିକୁ ଲିଭାନ୍ତୁ\?</string>
     <string name="stop">ବନ୍ଦ କର</string>
     <string name="max_retry_msg">ସର୍ବାଧିକ ପୁନଃଚେଷ୍ଟା</string>
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index ee5b4aacc..e34469eb1 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -395,7 +395,7 @@
     <string name="overwrite_failed">ਫਾਈਲ ਓਵਰਰਾਈਟ ਨਹੀਂ ਹੋ ਸਕਦੀ</string>
     <string name="download_already_pending">ਇਸ ਨਾਮ ਦਾ ਇੱਕ ਡਾਊਨਲੋਡ ਬਕਾਇਆ ਹੈ</string>
     <string name="error_postprocessing_stopped">ਫਾਈਲ ਤੇ ਕੰਮ ਕਰਦੇ ਸਮੇਂ ਨਿਊਪਾਈਪ ਬੰਦ ਕੀਤੀ ਗਈ</string>
-    <string name="error_insufficient_storage">ਡਿਵਾਈਸ ਤੇ ਕੋਈ ਜਗ੍ਹਾ ਨਹੀਂ ਬਚੀ ਹੈ</string>
+    <string name="error_insufficient_storage_left">ਡਿਵਾਈਸ ਤੇ ਕੋਈ ਜਗ੍ਹਾ ਨਹੀਂ ਬਚੀ ਹੈ</string>
     <string name="error_progress_lost">ਪ੍ਰਗਤੀ ਖਤਮ ਹੋ ਗਈ, ਕਿਉਂਕਿ ਫਾਈਲ ਮਿਟਾਈ ਗਈ</string>
     <string name="error_timeout">ਕੁਨੈਕਸ਼ਨ ਦਾ ਸਮਾਂ ਸਮਾਪਤ</string>
     <string name="confirm_prompt">ਕੀ ਤੁਸੀਂ ਆਪਣਾ ਡਾਊਨਲੋਡ ਇਤਿਹਾਸ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ ਜਾਂ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ\?</string>
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 2a7d80396..0212da91f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -401,7 +401,7 @@
     <string name="overwrite_failed">Nie udało się nadpisać pliku</string>
     <string name="download_already_pending">Plik o tej samej nazwie oczekuje na pobranie</string>
     <string name="error_postprocessing_stopped">NewPipe został zamknięty podczas pracy nad plikiem</string>
-    <string name="error_insufficient_storage">Brak miejsca na urządzeniu</string>
+    <string name="error_insufficient_storage_left">Brak miejsca na urządzeniu</string>
     <string name="error_progress_lost">Utracono postęp, ponieważ plik został usunięty</string>
     <string name="confirm_prompt">Czy chcesz wyczyścić historię pobierania, czy usunąć wszystkie pobrane pliki\?</string>
     <string name="enable_queue_limit">Ogranicz kolejkę pobierania</string>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index cca254aea..c0c20636b 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">O arquivo não pode ser sobrescrito</string>
     <string name="download_already_pending">Já existe um download pendente com este nome</string>
     <string name="error_postprocessing_stopped">O NewPipe foi fechado enquanto manipulava o arquivo</string>
-    <string name="error_insufficient_storage">Sem espaço disponível</string>
+    <string name="error_insufficient_storage_left">Sem espaço disponível</string>
     <string name="error_progress_lost">O progresso foi perdido pois o arquivo foi excluído</string>
     <string name="error_timeout">Tempo limite de conexão</string>
     <string name="confirm_prompt">Excluir todo o histórico de downloads ou excluir todos os arquivos baixados\?</string>
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 0dae4fc78..3a6fece0a 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -221,7 +221,7 @@
     <string name="feed_oldest_subscription_update">Última atualização: %s</string>
     <string name="import_data_title">Importar base de dados</string>
     <string name="error_report_title">Relatório de erro</string>
-    <string name="error_insufficient_storage">Não há espaço livre no dispositivo</string>
+    <string name="error_insufficient_storage_left">Não há espaço livre no dispositivo</string>
     <string name="max_retry_desc">Número máximo de tentativas antes de cancelar a descarga</string>
     <string name="player_recoverable_failure">A recuperar de um erro do reprodutor</string>
     <string name="duration_live">Em direto</string>
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 799166a89..554082a10 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">Não foi possível substituir o ficheiro</string>
     <string name="download_already_pending">Existe uma descarga pendente com este nome</string>
     <string name="error_postprocessing_stopped">NewPipe foi fechado enquanto trabalhava no ficheiro</string>
-    <string name="error_insufficient_storage">Não há espaço livre no dispositivo</string>
+    <string name="error_insufficient_storage_left">Não há espaço livre no dispositivo</string>
     <string name="error_progress_lost">Progresso perdido, o ficheiro foi eliminado</string>
     <string name="error_timeout">Ligação expirada</string>
     <string name="confirm_prompt">Deseja limpar o histórico de descargas ou remover todos os ficheiros descarregados\?</string>
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index bc3b81e1e..49a6b592b 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -475,7 +475,7 @@
     <string name="error_download_resource_gone">Nu se poate recupera această descărcare</string>
     <string name="error_timeout">Conexiunea a expirat</string>
     <string name="error_progress_lost">Progres pierdut, deoarece fișierul a fost șters</string>
-    <string name="error_insufficient_storage">Nu a mai rămas spațiu pe dispozitiv</string>
+    <string name="error_insufficient_storage_left">Nu a mai rămas spațiu pe dispozitiv</string>
     <string name="error_postprocessing_stopped">NewPipe a fost închis în timp ce lucra la fișier</string>
     <string name="error_postprocessing_failed">Post-procesarea a eșuat</string>
     <string name="error_http_not_found">Nu a fost găsit</string>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 32454ae72..bd7e5ce20 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -405,7 +405,7 @@
     <string name="overwrite_failed">не удаётся перезаписать файл</string>
     <string name="download_already_pending">В очереди уже есть загрузка с таким именем</string>
     <string name="error_postprocessing_stopped">NewPipe была закрыта во время работы над файлом</string>
-    <string name="error_insufficient_storage">Закончилось свободное место на устройстве</string>
+    <string name="error_insufficient_storage_left">Закончилось свободное место на устройстве</string>
     <string name="error_progress_lost">Прогресс потерян, так как файл был удалён</string>
     <string name="confirm_prompt">Действительно удалить историю загрузок и загруженные файлы\?</string>
     <string name="enable_queue_limit">Ограничить очередь загрузки</string>
diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml
index dab5815db..a531d14bc 100644
--- a/app/src/main/res/values-ryu/strings.xml
+++ b/app/src/main/res/values-ryu/strings.xml
@@ -392,7 +392,7 @@
     <string name="overwrite_failed">ファイルうわがきなやびらん</string>
     <string name="download_already_pending">いぬファイルめいぬダウンロードぬしでぃにしんこうちゅうやいびーん</string>
     <string name="error_postprocessing_stopped">ファイルぬさぎょうちゅうにNewPipeぬくーららりやびたん</string>
-    <string name="error_insufficient_storage">デバイスんかいにりらりょうぬあいびらん</string>
+    <string name="error_insufficient_storage_left">デバイスんかいにりらりょうぬあいびらん</string>
     <string name="error_progress_lost">ファイルぬさちゅるじょさったるたみ、しんこうじょうちゅーがうしならりやびたん</string>
     <string name="confirm_prompt">ダウンロードりりき、あらんでぃダウンロードさるファイルしーょうきょさびーが?</string>
     <string name="enable_queue_limit">ダウンロードキューぬせいぎん</string>
diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml
index 65dbffbae..a5959086e 100644
--- a/app/src/main/res/values-sat/strings.xml
+++ b/app/src/main/res/values-sat/strings.xml
@@ -708,7 +708,7 @@
     <string name="app_description">ᱟᱱᱰᱨᱚᱭᱮᱰ ᱨᱮ ᱞᱤᱵᱨᱮ ᱞᱟᱭᱤᱴᱣᱮᱴ ᱥᱴᱨᱤᱢᱤᱝ</string>
     <string name="metadata_tags">ᱴᱮᱜᱥ</string>
     <string name="metadata_privacy_public">ᱥᱚᱨᱠᱟᱨᱤ</string>
-    <string name="error_insufficient_storage">ᱚᱱᱚᱞ ᱨᱮ ᱡᱟᱭᱜᱟ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
+    <string name="error_insufficient_storage_left">ᱚᱱᱚᱞ ᱨᱮ ᱡᱟᱭᱜᱟ ᱵᱟᱹᱱᱩᱜᱼᱟ</string>
     <string name="detail_sub_channel_thumbnail_view_description">ᱪᱮᱱᱮᱞ ᱨᱮᱱᱟᱜ ᱟᱵᱟᱛᱟᱨ ᱛᱷᱩᱱᱤᱠᱟ</string>
     <string name="playlist_page_summary">ᱥᱮᱨᱮᱧ ᱞᱤᱥᱴᱤ ᱥᱟᱦᱴᱟ</string>
     <string name="never">ᱵᱟᱝ</string>
diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml
index b9178cf87..b39a041ee 100644
--- a/app/src/main/res/values-sc/strings.xml
+++ b/app/src/main/res/values-sc/strings.xml
@@ -316,7 +316,7 @@
     <string name="error_download_resource_gone">Impossìbile recuperare custu iscarrigamentu</string>
     <string name="error_timeout">Connessione iscadida</string>
     <string name="error_progress_lost">Su progressu s\'est pèrdidu, ca su documentu est istadu iscantzelladu</string>
-    <string name="error_insufficient_storage">Perunu ispàtziu abarradu in su dispositivu</string>
+    <string name="error_insufficient_storage_left">Perunu ispàtziu abarradu in su dispositivu</string>
     <string name="error_postprocessing_stopped">NewPipe est istadu serradu in su mentres chi fiat traballende a su documentu</string>
     <string name="error_postprocessing_failed">Post-protzessamentu fallidu</string>
     <string name="error_http_not_found">No agatadu</string>
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index a76bc1d4d..231de5fc4 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">súbor nemožno prepísať</string>
     <string name="download_already_pending">Súbor s rovnakým názvom už čaká na stiahnutie</string>
     <string name="error_postprocessing_stopped">NewPipe bol ukončený počas spracovávania súboru</string>
-    <string name="error_insufficient_storage">Máš plnú pamäť</string>
+    <string name="error_insufficient_storage_left">Máš plnú pamäť</string>
     <string name="error_progress_lost">Nemožno pokračovať, súbor bol vymazaný</string>
     <string name="error_timeout">Spojenie vypršalo</string>
     <string name="confirm_prompt">Chcete vymazať históriu sťahovania alebo odstrániť všetky stiahnuté súbory\?</string>
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 4912db031..b0fcec406 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -211,7 +211,7 @@
     <string name="clear_download_history">Počisti zgodovino prenosa</string>
     <string name="error_download_resource_gone">Ni mogoče povrniti prenos</string>
     <string name="error_progress_lost">Napredek je izgubljen, ker je bila datoteka izbrisana</string>
-    <string name="error_insufficient_storage">Ni več prostora v vaši napravi</string>
+    <string name="error_insufficient_storage_left">Ni več prostora v vaši napravi</string>
     <string name="error_postprocessing_stopped">NewPipe se je zaprl medtem ko je delal z datoteko</string>
     <string name="error_http_not_found">Ni najden</string>
     <string name="error_http_no_content">Strežnik ne pošilja informacij</string>
diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml
index 247ff682a..e37c3a36d 100644
--- a/app/src/main/res/values-so/strings.xml
+++ b/app/src/main/res/values-so/strings.xml
@@ -520,7 +520,7 @@
     <string name="error_download_resource_gone">Dajintan lama hagaajin karo</string>
     <string name="error_timeout">Ku xidhidii ayaa wakhtigii ka dhacay</string>
     <string name="error_progress_lost">Hawshii socotay way kala kacday, sababtoo ah shayga waa lala saaray</string>
-    <string name="error_insufficient_storage">Meel banaan oo wax lagu kaydiyo aalaada kuma hadhin</string>
+    <string name="error_insufficient_storage_left">Meel banaan oo wax lagu kaydiyo aalaada kuma hadhin</string>
     <string name="error_postprocessing_stopped">NewPipe waxaa la xidhay asagoo fayl ka shaqaynaya</string>
     <string name="error_postprocessing_failed">Habayntii way guuldareystay</string>
     <string name="error_http_not_found">Lama helin</string>
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index dc8ca02f4..5196d606a 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -82,7 +82,7 @@
     <string name="error_download_resource_gone">Nuk mund të rikuperohet ky shkarkim</string>
     <string name="error_timeout">Koha e lidhjes skadoi</string>
     <string name="error_progress_lost">Progresi humbi, pasi skedari është fshirë</string>
-    <string name="error_insufficient_storage">Nuk ka vend bosh në pajisje</string>
+    <string name="error_insufficient_storage_left">Nuk ka vend bosh në pajisje</string>
     <string name="error_postprocessing_stopped">NewPipe u mbyll ndërkohë që po punohej mbi skedarin</string>
     <string name="error_postprocessing_failed">Procesimi dështoi</string>
     <string name="error_http_not_found">Nuk u gjet</string>
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 66659c77a..fc68ce5eb 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -237,7 +237,7 @@
     <string name="error_download_resource_gone">Није могуће опоравити ово преузимање</string>
     <string name="error_timeout">Веза је истекла</string>
     <string name="error_progress_lost">Напредак је изгубљен, јер је фајл избрисан</string>
-    <string name="error_insufficient_storage">Недовољно меморије на уређају</string>
+    <string name="error_insufficient_storage_left">Недовољно меморије на уређају</string>
     <string name="error_postprocessing_stopped">NewPipe је затворен док је радио на фајлу</string>
     <string name="error_postprocessing_failed">Накнадна обрада није успела</string>
     <string name="error_http_not_found">Није пронађено</string>
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 2f66aa0de..be8e2894c 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -503,7 +503,7 @@
     <string name="error_download_resource_gone">Kan inte återställa den här hämtningen</string>
     <string name="error_timeout">Anslutnings avbrott</string>
     <string name="error_progress_lost">Framsteg förlorat, för att filen blev borttagen</string>
-    <string name="error_insufficient_storage">Inget utrymme kvar på enhet</string>
+    <string name="error_insufficient_storage_left">Inget utrymme kvar på enhet</string>
     <string name="error_postprocessing_stopped">NewPipe stängdes under arbete med en fil</string>
     <string name="error_report_open_github_notice">Vänligen kontrollera om en felrapport som tar upp din krasch redan finns. Att skapa ärenden när en felrapport redan finns, tar av den tid som vi annars kunde ha ägnat åt att fixa den faktiska buggen.</string>
     <string name="error_report_open_issue_button_text">Rapportera på GitHub</string>
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 056434a4d..dfac4fb47 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -390,7 +390,7 @@
     <string name="overwrite_failed">dosyanın üzerine yazılamaz</string>
     <string name="download_already_pending">Bu isim ile bekleyen bir indirme var</string>
     <string name="error_postprocessing_stopped">NewPipe bu dosya üzerinde çalışırken kapandı</string>
-    <string name="error_insufficient_storage">Aygıt üzerinde yer yok</string>
+    <string name="error_insufficient_storage_left">Aygıt üzerinde yer yok</string>
     <string name="error_progress_lost">İlerleme kaybedildi, çünkü dosya silinmiş</string>
     <string name="error_timeout">Bağlantı zaman aşımı</string>
     <string name="confirm_prompt">İndirme geçmişinizi temizlemek veya indirilen tüm dosyaları silmek istiyor musunuz\?</string>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 76813a04f..9348c448d 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">не можу перезаписати файл</string>
     <string name="download_already_pending">Завантаження з такою назвою вже додано в чергу</string>
     <string name="error_postprocessing_stopped">NewPipe був закритий під час роботи над файлом</string>
-    <string name="error_insufficient_storage">На пристрої не залишилося вільного місця</string>
+    <string name="error_insufficient_storage_left">На пристрої не залишилося вільного місця</string>
     <string name="error_progress_lost">Прогрес втрачено через видалення файлу</string>
     <string name="error_timeout">Час очікування з\'єднання вичерпано</string>
     <string name="confirm_prompt">Очистити історію завантажень чи завантажені файли\?</string>
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index 1cd5de013..d70927121 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -390,7 +390,7 @@
     <string name="overwrite_failed">فائل برتحریر نہیں کر سکتا</string>
     <string name="download_already_pending">اس نام کے ساتھ ڈاؤن لوڈ زیر التوا ہے</string>
     <string name="error_postprocessing_stopped">فائل پر کام کرنے کے دوران نیو پائپ بند کردی گئی تھی</string>
-    <string name="error_insufficient_storage">آلہ میں کوئی جگہ نہیں بچی</string>
+    <string name="error_insufficient_storage_left">آلہ میں کوئی جگہ نہیں بچی</string>
     <string name="error_progress_lost">پیشرفت ختم ہوگئی، کیونکہ فائل کو حذف کردیا گیا تھا</string>
     <string name="error_timeout">رابطے کا وقت ختم</string>
     <string name="confirm_prompt">کیا آپ اپنی ڈاؤن لوڈ کی سرگزشت کو صاف کرنا چاہتے ہیں یا ڈاؤن لوڈ کی گئی تمام فائلوں کو حذف کرنا چاہتے ہیں؟</string>
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index bac361395..cdd6f6e31 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -392,7 +392,7 @@
     <string name="overwrite_failed">Không thể ghi đè lên tệp</string>
     <string name="download_already_pending">Có một bản tải xuống đang chờ xử lí với tên này</string>
     <string name="error_postprocessing_stopped">Newpipe đã bị đóng khi đang xử lí tệp</string>
-    <string name="error_insufficient_storage">Không đủ dung lượng trên máy</string>
+    <string name="error_insufficient_storage_left">Không đủ dung lượng trên máy</string>
     <string name="error_progress_lost">Quá trình tải bị hủy, vì tập tin đã bị xoá</string>
     <string name="error_timeout">Kết nối hết thời gian</string>
     <string name="confirm_prompt">Bạn muốn xóa lịch sử tải về hay xóa tất cả những file đã tải về\?</string>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index f1d3fbf61..89c215943 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -393,7 +393,7 @@
     <string name="overwrite_failed">无法覆盖文件</string>
     <string name="download_already_pending">已暂停下载包含此名称的任务</string>
     <string name="error_postprocessing_stopped">NewPipe 在处理文件时被关闭</string>
-    <string name="error_insufficient_storage">设备上没有剩余储存空间</string>
+    <string name="error_insufficient_storage_left">设备上没有剩余储存空间</string>
     <string name="error_progress_lost">进度丢失,文件已被删除</string>
     <string name="error_timeout">连接超时</string>
     <string name="confirm_prompt">是否清空下载记录或删除所有下载的文件?</string>
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index e9eb5bf12..c91731314 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -454,7 +454,7 @@
     <string name="error_path_creation">建立唔到呢個目的地資料夾</string>
     <string name="error_ssl_exception">建立唔到安全連線</string>
     <string name="start_here_on_background">喺幕後開始播放</string>
-    <string name="error_insufficient_storage">部機冇晒位</string>
+    <string name="error_insufficient_storage_left">部機冇晒位</string>
     <string name="max_retry_msg">頂櫳重試幾多次</string>
     <string name="pause_downloads_on_mobile_desc">若然有機會用到流動數據嘅時候,可能會用得著,雖則有啲下載冇得暫停</string>
     <string name="enable_queue_limit">輪住嚟下載</string>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 382a95479..f2eb503ab 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -390,7 +390,7 @@
     <string name="overwrite_failed">無法覆寫檔案</string>
     <string name="download_already_pending">已有擱置中的下載與此同名</string>
     <string name="error_postprocessing_stopped">NewPipe 在處理檔案時被關閉</string>
-    <string name="error_insufficient_storage">裝置上沒有剩餘的空間</string>
+    <string name="error_insufficient_storage_left">裝置上沒有剩餘的空間</string>
     <string name="error_progress_lost">進度遺失,因為檔案已被刪除</string>
     <string name="confirm_prompt">您想要清除您的下載歷史記錄,還是刪除所有已下載的檔案?</string>
     <string name="enable_queue_limit">限制下載佇列</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 281df95a4..30a85f3c7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -616,7 +616,8 @@
     <string name="error_http_not_found">Not found</string>
     <string name="error_postprocessing_failed">Post-processing failed</string>
     <string name="error_postprocessing_stopped">NewPipe was closed while working on the file</string>
-    <string name="error_insufficient_storage">No space left on device</string>
+    <string name="error_insufficient_storage">Not enough free space on device</string>
+    <string name="error_insufficient_storage_left">No space left on device</string>
     <string name="error_progress_lost">Progress lost, because the file was deleted</string>
     <string name="error_timeout">Connection timeout</string>
     <string name="error_download_resource_gone">Cannot recover this download</string>

From bc7c3f48ad6293f960b4cff25d6fc940ff60d490 Mon Sep 17 00:00:00 2001
From: Mohammed Anas <triallax@tutanota.com>
Date: Tue, 26 Mar 2024 19:53:45 +0000
Subject: [PATCH 100/141] Fix GitHub question discussion form

The name and description fields are both invalid here.
---
 .github/DISCUSSION_TEMPLATE/questions.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml
index befb6280e..bb7894625 100644
--- a/.github/DISCUSSION_TEMPLATE/questions.yml
+++ b/.github/DISCUSSION_TEMPLATE/questions.yml
@@ -1,5 +1,3 @@
-name: Question
-description: Ask about anything NewPipe-related
 labels: [question]
 body:
   - type: markdown

From 1732584e5e60a1c50bed4d67aaf50ea51d86e824 Mon Sep 17 00:00:00 2001
From: Hosted Weblate <hosted@weblate.org>
Date: Wed, 20 Mar 2024 21:26:58 +0100
Subject: [PATCH 101/141] Translated using Weblate (Danish)

Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: cat <158170307+cultcats@users.noreply.github.com>
---
 app/src/main/res/values-da/strings.xml | 434 +++++++++++++------------
 1 file changed, 231 insertions(+), 203 deletions(-)

diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index c3842e3b8..f48ed7b3b 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -3,7 +3,7 @@
     <string name="main_bg_subtitle">Tryk på forstørrelsesglasset for at komme i gang.</string>
     <string name="upload_date_text">Udgivet den %1$s</string>
     <string name="no_player_found">Ingen streamafspiller blev fundet. Installér VLC\?</string>
-    <string name="no_player_found_toast">Ingen streamafspiller fundet (du kan installere VLC for at afspille den).</string>
+    <string name="no_player_found_toast">Ingen streamafspiller blev fundet (du kan installere VLC for at afspille den).</string>
     <string name="install">Installér</string>
     <string name="cancel">Annullér</string>
     <string name="open_in_browser">Åbn i browser</string>
@@ -39,16 +39,16 @@
     <string name="download_path_audio_summary">Hentede lydfiler gemmes her</string>
     <string name="download_path_audio_dialog_title">Vælg lagringsmappe til lydfiler</string>
     <string name="default_resolution_title">Standardopløsning</string>
-    <string name="default_popup_resolution_title">Standardopløsning for popup</string>
+    <string name="default_popup_resolution_title">Standardopløsning til popup</string>
     <string name="show_higher_resolutions_title">Vis højere opløsninger</string>
     <string name="show_higher_resolutions_summary">Kun nogle enheder kan afspille 2K-/4K-videoer</string>
     <string name="play_with_kodi_title">Afspil med Kodi</string>
     <string name="kore_not_found">Installér manglende Kore-app?</string>
     <string name="show_play_with_kodi_title">Vis valgmuligheden \"Afspil med Kodi\"</string>
-    <string name="show_play_with_kodi_summary">Vis en knap til at afspille en video via Kodi-mediecenteret</string>
+    <string name="show_play_with_kodi_summary">Vis en mulighed for at afspille en video via Kodi-mediecenter</string>
     <string name="play_audio">Lyd</string>
-    <string name="default_audio_format_title">Standardformat for lydfiler</string>
-    <string name="default_video_format_title">Standardformat for videofiler</string>
+    <string name="default_audio_format_title">Standard lydformat</string>
+    <string name="default_video_format_title">Standard videoformat</string>
     <string name="theme_title">Tema</string>
     <string name="light_theme_title">Lys</string>
     <string name="dark_theme_title">Mørk</string>
@@ -56,11 +56,11 @@
     <string name="popup_remember_size_pos_title">Husk popup-egenskaber</string>
     <string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af popup-afspiller</string>
     <string name="use_inexact_seek_title">Brug hurtig og upræcis søgning</string>
-    <string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist. Søgninger på 5, 15 eller 25 sekunder fungerer ikke med denne indstilling slået til</string>
-    <string name="thumbnail_cache_wipe_complete_notice">Billedcache slettet</string>
-    <string name="metadata_cache_wipe_title">Slet metadata-cache</string>
+    <string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde positioner hurtigere, men med nedsat præcision. Søgning i 5, 15 eller 25 sekunder fungerer ikke med dette</string>
+    <string name="thumbnail_cache_wipe_complete_notice">Billedcache ryddet</string>
+    <string name="metadata_cache_wipe_title">Ryd metadata-cache</string>
     <string name="metadata_cache_wipe_summary">Fjern alle cached websidedata</string>
-    <string name="metadata_cache_wipe_complete_notice">Metadata-cache slettet</string>
+    <string name="metadata_cache_wipe_complete_notice">Metadata-cache ryddet</string>
     <string name="auto_queue_title">Føj automatisk næste stream til kø</string>
     <string name="auto_queue_summary">Fortsæt en afspilningskø, der afsluttes (ikke-gentagende), ved at tilføje en lignende stream</string>
     <string name="show_search_suggestions_title">Søgeforslag</string>
@@ -113,57 +113,57 @@
     <string name="file">Fil</string>
     <string name="notification_channel_name">NewPipe-notifikation</string>
     <string name="notification_channel_description">Notifikationer for NewPipes afspiller</string>
-    <string name="app_update_notification_channel_name">Notifikation om opdatering af app</string>
-    <string name="app_update_notification_channel_description">Notifikationer for nye NewPipe versioner</string>
+    <string name="app_update_notification_channel_name">Notifikation om app-opdatering</string>
+    <string name="app_update_notification_channel_description">Notifikationer for nye NewPipe-versioner</string>
     <string name="unknown_content">[Ukendt]</string>
     <string name="switch_to_background">Skift til baggrund</string>
     <string name="switch_to_popup">Skift til popup</string>
     <string name="switch_to_main">Skift til hovedafspiller</string>
     <string name="import_data_title">Importér database</string>
     <string name="export_data_title">Eksportér database</string>
-    <string name="import_data_summary">Overskriver din nuværende historik, abonnementer, spillelister og (hvis det ønskes) indstillinger</string>
-    <string name="export_data_summary">Eksportér historik, abonnementer, spillelister og indstillinger</string>
+    <string name="import_data_summary">Overskriver din nuværende historik, abonnementer, playlister og (hvis det ønskes) indstillinger</string>
+    <string name="export_data_summary">Eksportér historik, abonnementer, playlister og indstillinger</string>
     <string name="clear_views_history_title">Ryd visningshistorik</string>
     <string name="clear_views_history_summary">Sletter historikken over afspillede streams og afspilningspositionerne</string>
     <string name="delete_view_history_alert">Slet hele visningshistorikken\?</string>
-    <string name="watch_history_deleted">Visningshistorikken blev slettet</string>
+    <string name="watch_history_deleted">Visningshistorik slettet</string>
     <string name="clear_search_history_title">Ryd søgehistorik</string>
-    <string name="clear_search_history_summary">Sletter historikken for søgeord</string>
-    <string name="delete_search_history_alert">Slet hele søgehistorikken\?</string>
-    <string name="search_history_deleted">Søgehistorikken blev slettet</string>
+    <string name="clear_search_history_summary">Sletter historik for søgeord</string>
+    <string name="delete_search_history_alert">Slet hele søgehistorik?</string>
+    <string name="search_history_deleted">Søgehistorik slettet</string>
     <string name="general_error">Fejl</string>
     <string name="download_to_sdcard_error_title">Eksternt lager utilgængeligt</string>
-    <string name="download_to_sdcard_error_message">Det er endnu ikke muligt at downloade til et eksternt SD-kort. Nulstil download-mappens placering\?</string>
+    <string name="download_to_sdcard_error_message">Det er endnu ikke muligt at hente til et eksternt SD-kort. Nulstil lagringsmappens placering?</string>
     <string name="network_error">Netværksfejl</string>
     <string name="could_not_load_thumbnails">Kunne ikke indlæse alle miniaturebilleder</string>
-    <string name="parsing_error">Kunne ikke analysere websted</string>
-    <string name="content_not_available">Indhold ikke tilgængeligt</string>
-    <string name="could_not_setup_download_menu">Kunne ikke oprette downloadmenu</string>
-    <string name="app_ui_crash">App/brugergrænseflade gik ned</string>
+    <string name="parsing_error">Kunne ikke analysere webside</string>
+    <string name="content_not_available">Indhold utilgængeligt</string>
+    <string name="could_not_setup_download_menu">Kunne ikke opsætte download-menu</string>
+    <string name="app_ui_crash">App/UI gik ned</string>
     <string name="player_stream_failure">Kunne ikke afspille denne stream</string>
     <string name="player_unrecoverable_failure">Uoprettelig afspillerfejl opstod</string>
-    <string name="player_recoverable_failure">Prøver at genoprette efter afspillerfejl</string>
+    <string name="player_recoverable_failure">Genopretter efter afspillerfejl</string>
     <string name="external_player_unsupported_link_type">Eksterne afspillere understøtter ikke disse typer af links</string>
     <string name="video_streams_empty">Ingen videostreams fundet</string>
     <string name="audio_streams_empty">Ingen lydstreams fundet</string>
     <string name="invalid_directory">Mappen findes ikke</string>
     <string name="invalid_source">Fil eller indholdskilde findes ikke</string>
-    <string name="invalid_file">Filen eksister ikke eller der mangler rettigheder til at læse eller skrive til den</string>
-    <string name="file_name_empty_error">Filavnet kan ikke være tomt</string>
+    <string name="invalid_file">Filen findes ikke, ellers mangler der tilladelse til at læse eller skrive til den</string>
+    <string name="file_name_empty_error">Filnavn må ikke være tomt</string>
     <string name="error_occurred_detail">Der opstod en fejl: %1$s</string>
-    <string name="no_streams_available_download">Ingen streams er tilgængelige for download</string>
+    <string name="no_streams_available_download">Ingen streams tilgængelige til hentning</string>
     <string name="saved_tabs_invalid_json">Bruger standardfaner pga. fejl ved indlæsning af gemte faner</string>
-    <string name="restore_defaults">Genskab standardindstillinger</string>
-    <string name="restore_defaults_confirmation">Vil du genoprette standardindstillingerne\?</string>
+    <string name="restore_defaults">Gendan standardindstillinger</string>
+    <string name="restore_defaults_confirmation">Vil du gendanne standardindstillinger?</string>
     <string name="sorry_string">Undskyld, dette skulle ikke være sket.</string>
-    <string name="error_report_button_text">Rapporter via e-mail</string>
+    <string name="error_report_button_text">Rapportér via e-mail</string>
     <string name="error_snackbar_message">Beklager, noget gik galt.</string>
-    <string name="error_snackbar_action">Rapporter</string>
-    <string name="what_device_headline">Information:</string>
+    <string name="error_snackbar_action">Rapportér</string>
+    <string name="what_device_headline">Info:</string>
     <string name="what_happened_headline">Hvad skete der:</string>
     <string name="your_comment">Din kommentar (på engelsk):</string>
     <string name="error_details_headline">Detaljer:</string>
-    <string name="detail_thumbnail_view_description">Afspil video, længde:</string>
+    <string name="detail_thumbnail_view_description">Afspil video, varighed:</string>
     <string name="detail_uploader_thumbnail_view_description">Uploaders profilbillede</string>
     <string name="detail_likes_img_view_description">Synes godt om</string>
     <string name="detail_dislikes_img_view_description">Kan ikke lide</string>
@@ -173,7 +173,7 @@
     <string name="audio">Lyd</string>
     <string name="retry">Prøv igen</string>
     <string name="no_subscribers">Ingen abonnenter</string>
-    <string name="subscribers_count_not_available">Antallet af abonnenter er ikke tilgængeligt</string>
+    <string name="subscribers_count_not_available">Antal abonnenter utilgængeligt</string>
     <string name="no_views">Ingen visninger</string>
     <plurals name="views">
         <item quantity="one">%s visning</item>
@@ -190,17 +190,17 @@
     <string name="msg_name">Filnavn</string>
     <string name="msg_threads">Tråde</string>
     <string name="msg_error">Fejl</string>
-    <string name="msg_running">NewPiper downloader</string>
+    <string name="msg_running">NewPipe henter</string>
     <string name="msg_running_detail">Tryk for detaljer</string>
     <string name="msg_wait">Vent venligst…</string>
-    <string name="msg_copied">Kopieret til udklipsholderen</string>
-    <string name="no_available_dir">Vælg senere en tilgængelig downloadmappe i indstillingerne</string>
-    <string name="msg_popup_permission">Denne tilladelse behøves for
-\nat åbne i pop op-tilstand</string>
+    <string name="msg_copied">Kopieret til udklipsholder</string>
+    <string name="no_available_dir">Angiv venligst en lagringsmappe senere i indstillinger</string>
+    <string name="msg_popup_permission">Denne tilladelse er nødvendig for
+\nat åbne i popup-tilstand</string>
     <string name="one_item_deleted">1 element slettet.</string>
     <string name="title_activity_recaptcha">reCAPTCHA-udfordring</string>
-    <string name="recaptcha_request_toast">Der blev anmodet om en reCAPTCHA-udfordring</string>
-    <string name="settings_category_downloads_title">Download</string>
+    <string name="recaptcha_request_toast">reCAPTCHA-udfordring anmodet</string>
+    <string name="settings_category_downloads_title">Hent</string>
     <string name="settings_file_charset_title">Tilladte tegn i filnavne</string>
     <string name="settings_file_replacement_character_summary">Ugyldige tegn bliver erstattet med denne værdi</string>
     <string name="settings_file_replacement_character_title">Erstatningstegn</string>
@@ -209,30 +209,30 @@
     <string name="title_activity_about">Om NewPipe</string>
     <string name="title_licenses">Tredjepartslicenser</string>
     <string name="copyright" formatted="true">© %1$s af %2$s under %3$s</string>
-    <string name="tab_about">Om &amp; Ofte stillede spørgsmål</string>
+    <string name="tab_about">Om &amp; Ofte Stillede Spørgsmål</string>
     <string name="tab_licenses">Licenser</string>
     <string name="app_description">Åben letvægtsstreaming på Android.</string>
     <string name="contribution_title">Bidrag til projektet</string>
-    <string name="contribution_encouragement">Hvad enten du har idéer til oversættelse, designændringer, kodeoprydning eller virkelig tunge kodeændringer, så er hjælp altid velkommen. Jo mere der bliver gjort, jo bedre bliver det!</string>
+    <string name="contribution_encouragement">Uanset om du har idéer til oversættelse, designændringer, oprydning i koden eller virkelig store kodeændringer, er hjælp altid velkommen. Jo mere der bliver gjort, jo bedre bliver det!</string>
     <string name="view_on_github">Se på GitHub</string>
-    <string name="donation_title">Doner</string>
-    <string name="donation_encouragement">NewPipe er udviklet af frivillige, der bruger deres fritid på at give dig den bedst mulige brugeroplevelse. Giv noget tilbage for at hjælpe NewPipes udviklere til at gøre appen endnu bedre, mens de nyder en kop kaffe.</string>
+    <string name="donation_title">Donér</string>
+    <string name="donation_encouragement">NewPipe er udviklet af frivillige, der bruger deres fritid på at give dig den bedste brugeroplevelse. Giv noget tilbage og hjælp udviklerne med at gøre NewPipe endnu bedre, mens de nyder en kop kaffe.</string>
     <string name="give_back">Giv noget tilbage</string>
-    <string name="website_title">Websted</string>
-    <string name="website_encouragement">Besøg NewPipes websted for mere information og nyheder.</string>
-    <string name="privacy_policy_title">NewPipes fortrolighedspolitik</string>
-    <string name="read_privacy_policy">Læs fortrolighedspolitik</string>
-    <string name="app_license_title">NewPipes licens</string>
-    <string name="app_license">NewPipe er copyleft, fri software: Du kan bruge, studere, dele og forbedre den som du vil. Specifikt kan du redistribuere og/eller ændre den under betingelserne i GNU General Public License som udgivet af Free Software Foundation, enten version 3 af licensen eller (efter dit ønske) en vilkårlig senere version.</string>
+    <string name="website_title">Webside</string>
+    <string name="website_encouragement">Besøg NewPipes webside for mere information og nyheder.</string>
+    <string name="privacy_policy_title">NewPipes Privatlivspolitik</string>
+    <string name="read_privacy_policy">Læs privatlivspolitik</string>
+    <string name="app_license_title">NewPipes Licens</string>
+    <string name="app_license">NewPipe er copyleft, fri software: Du kan bruge, studere, dele og forbedre den, som du vil. Specifikt kan du redistribuere og/eller ændre den under betingelserne i GNU General Public License som udgivet af Free Software Foundation, enten version 3 af licensen eller (efter dit ønske) en vilkårlig senere version.</string>
     <string name="read_full_license">Læs licens</string>
     <string name="title_activity_history">Historik</string>
     <string name="action_history">Historik</string>
     <string name="delete_item_search_history">Vil du slette dette element fra søgehistorikken\?</string>
-    <string name="title_last_played">Sidst afspillet</string>
-    <string name="title_most_played">Mest spillede</string>
-    <string name="main_page_content">Indhold af hovedsiden</string>
+    <string name="title_last_played">Sidst Afspillet</string>
+    <string name="title_most_played">Mest Afspillet</string>
+    <string name="main_page_content">Indhold af hovedside</string>
     <string name="main_page_content_summary">Hvilke faner vises på hovedsiden</string>
-    <string name="blank_page_summary">Tom side</string>
+    <string name="blank_page_summary">Tom Side</string>
     <string name="kiosk_page_summary">Kioskside</string>
     <string name="channel_page_summary">Kanalside</string>
     <string name="select_a_channel">Vælg en kanal</string>
@@ -240,7 +240,7 @@
     <string name="select_a_kiosk">Vælg en kiosk</string>
     <string name="export_complete_toast">Eksporteret</string>
     <string name="import_complete_toast">Importeret</string>
-    <string name="no_valid_zip_file">Ikke en gyldig ZIP-fil</string>
+    <string name="no_valid_zip_file">Ingen gyldig ZIP-fil</string>
     <string name="could_not_import_all_files">Advarsel: Kunne ikke importere alle filer.</string>
     <string name="override_current_data">Dette vil overskrive dine nuværende indstillinger.</string>
     <string name="import_settings">Vil du også importere indstillinger\?</string>
@@ -250,47 +250,47 @@
     <string name="play_queue_remove">Fjern</string>
     <string name="play_queue_stream_detail">Detaljer</string>
     <string name="play_queue_audio_settings">Lydindstillinger</string>
-    <string name="hold_to_append">Hold for at føje til kø</string>
-    <string name="preferred_open_action_settings_title">Foretrukket \'åbn\'-handling</string>
+    <string name="hold_to_append">Hold for at sætte i kø</string>
+    <string name="preferred_open_action_settings_title">Foretrukken \'åbn\'-handling</string>
     <string name="video_player">Videoafspiller</string>
     <string name="background_player">Baggrundsafspiller</string>
-    <string name="popup_player">Pop op-afspiller</string>
+    <string name="popup_player">Popup-afspiller</string>
     <string name="always_ask_open_action">Spørg altid</string>
-    <string name="preferred_player_fetcher_notification_title">Henter info …</string>
+    <string name="preferred_player_fetcher_notification_title">Henter info…</string>
     <string name="preferred_player_fetcher_notification_message">Indlæser det ønskede indhold</string>
-    <string name="create_playlist">Ny spilleliste</string>
+    <string name="create_playlist">Ny Playliste</string>
     <string name="rename_playlist">Omdøb</string>
     <string name="name">Navn</string>
-    <string name="add_to_playlist">Føj til spilleliste</string>
-    <string name="delete_playlist_prompt">Slet denne spilleliste\?</string>
-    <string name="playlist_creation_success">Spilleliste oprettet</string>
-    <string name="caption_none">Ingen undertekster</string>
+    <string name="add_to_playlist">Føj til playliste</string>
+    <string name="delete_playlist_prompt">Slet denne playliste?</string>
+    <string name="playlist_creation_success">Playliste oprettet</string>
+    <string name="caption_none">Ingen Undertekster</string>
     <string name="resize_fit">Tilpas</string>
     <string name="resize_fill">Udfyld</string>
     <string name="resize_zoom">Zoom</string>
     <string name="caption_auto_generated">Autogenereret</string>
     <string name="caption_setting_title">Undertekster</string>
-    <string name="export_to">Eksporter til</string>
+    <string name="export_to">Eksportér til</string>
     <string name="playback_reset">Nulstil</string>
-    <string name="accept">Accepter</string>
+    <string name="accept">Acceptér</string>
     <string name="decline">Afvis</string>
     <string name="limit_data_usage_none_description">Ingen begrænsning</string>
-    <string name="limit_mobile_data_usage_title">Begræns opløsning når der bruges mobildata</string>
+    <string name="limit_mobile_data_usage_title">Begræns opløsning, når mobildata bruges</string>
     <string name="updates_setting_title">Opdateringer</string>
     <string name="list_view_mode">Listevisning</string>
     <string name="list">Liste</string>
     <string name="grid">Gitter</string>
     <string name="auto">Automatisk</string>
     <string name="missions_header_finished">Færdig</string>
-    <string name="missions_header_pending">Afventning</string>
+    <string name="missions_header_pending">Afventer</string>
     <string name="post_processing">efterbehandling</string>
-    <string name="enqueue">Læg i kø</string>
+    <string name="enqueue">Sæt i kø</string>
     <string name="permission_denied">Handling afvist af systemet</string>
-    <string name="download_failed">Download fejlede</string>
-    <string name="generate_unique_name">Generer unikt navn</string>
+    <string name="download_failed">Hentning mislykkedes</string>
+    <string name="generate_unique_name">Generér unikt navn</string>
     <string name="overwrite">Overskriv</string>
     <string name="overwrite_unrelated_warning">En fil med dette navn eksisterer allerede</string>
-    <string name="overwrite_finished_warning">En downloadet fil med dette navn eksisterer allerede</string>
+    <string name="overwrite_finished_warning">En hentet fil med dette navn eksisterer allerede</string>
     <string name="download_already_running">Der er en download i gang med dette navn</string>
     <string name="show_error">Vis fejl</string>
     <string name="error_file_creation">Filen kan ikke oprettes</string>
@@ -301,10 +301,10 @@
     <string name="error_http_no_content">Serveren sender ikke data</string>
     <string name="error_http_unsupported_range">Serveren accepterer ikke multitrådede downloads; prøv igen med @string/msg_threads = 1</string>
     <string name="error_http_not_found">Ikke fundet</string>
-    <string name="error_postprocessing_failed">Efterbehandling fejlede</string>
+    <string name="error_postprocessing_failed">Efterbehandling mislykkedes</string>
     <string name="stop">Stop</string>
     <string name="events">Hændelser</string>
-    <string name="empty_list_subtitle">Intet at se her</string>
+    <string name="empty_list_subtitle">Ikke andet end fårekyllinger her</string>
     <string name="short_thousand">t</string>
     <string name="short_million">mio.</string>
     <string name="short_billion">mia.</string>
@@ -316,55 +316,55 @@
     <string name="subscriptions_import_unsuccessful">Kunne ikke importere abonnementer</string>
     <string name="subscriptions_export_unsuccessful">Kunne ikke eksportere abonnementer</string>
     <string name="conferences">Konferencer</string>
-    <string name="start_here_on_background">Start afspilningen i baggrunden</string>
-    <string name="start_here_on_popup">Start afspilning i et pop op</string>
-    <string name="drawer_open">Åbn skuffe</string>
-    <string name="drawer_close">Luk skuffe</string>
-    <string name="info_labels">Hvad:\\nForespørgsel:\\nIndholdssprog:\\nIndholdsland:\\nAppsprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:</string>
-    <string name="preferred_open_action_settings_summary">Standardhandling når indhold åbnes – %s</string>
-    <string name="set_as_playlist_thumbnail">Anvend som spillelistens miniaturebillede</string>
-    <string name="bookmark_playlist">Bogmærk spilleliste</string>
-    <string name="unbookmark_playlist">Fjern bogmærke</string>
-    <string name="playlist_add_stream_success">Føjet til spillelisten</string>
-    <string name="playlist_thumbnail_change_success">Miniaturebillede for spilleliste ændret.</string>
+    <string name="start_here_on_background">Start afspilning i baggrunden</string>
+    <string name="start_here_on_popup">Start afspilning i et popup</string>
+    <string name="drawer_open">Åbn Skuffe</string>
+    <string name="drawer_close">Luk Skuffe</string>
+    <string name="info_labels">Hvad:\\nForespørgsel:\\nIndholdssprog:\\nIndholdsland:\\nApp-sprog:\\nTjeneste:\\nGMT-tid:\\nPakke:\\nVersion:\\nOS-version:</string>
+    <string name="preferred_open_action_settings_summary">Standardhandling ved åbning af indhold — %s</string>
+    <string name="set_as_playlist_thumbnail">Anvend som playlistens miniaturebillede</string>
+    <string name="bookmark_playlist">Bogmærk Playliste</string>
+    <string name="unbookmark_playlist">Fjern Bogmærke</string>
+    <string name="playlist_add_stream_success">Føjet til playliste</string>
+    <string name="playlist_thumbnail_change_success">Ændret playlistens miniaturebillede.</string>
     <string name="caption_setting_description">Ændr undertekststørrelse og baggrundsstil. Kræver genstart af appen for at træde i kraft</string>
-    <string name="enable_leak_canary_summary">Monitorering for hukommelseslækager kan få appen til ikke at svare under heap dumping</string>
-    <string name="enable_disposed_exceptions_title">Rapporter out-of-lifecycle-fejl</string>
-    <string name="import_title">Importer</string>
-    <string name="import_from">Importer fra</string>
-    <string name="import_ongoing">Importerer …</string>
-    <string name="export_ongoing">Eksporterer …</string>
-    <string name="import_file_title">Importer fil</string>
+    <string name="enable_leak_canary_summary">Overvågning af hukommelseslækager kan medføre, at appen ikke reagerer, når heap-dumpning udføres</string>
+    <string name="enable_disposed_exceptions_title">Rapportér out-of-lifecycle-fejl</string>
+    <string name="import_title">Importér</string>
+    <string name="import_from">Importér fra</string>
+    <string name="import_ongoing">Importerer…</string>
+    <string name="export_ongoing">Eksporterer…</string>
+    <string name="import_file_title">Importér fil</string>
     <string name="previous_export">Forrige eksport</string>
-    <string name="import_youtube_instructions">Importer YouTube-abonnementer ved at downloade eksportfilen:
+    <string name="import_youtube_instructions">Importér YouTube-abonnementer ved at hente eksportfilen:
 \n
 \n1. Gå til denne webadresse: %1$s
-\n2. Log ind når du bliver bedt om det
-\n3. Klik på \"Alle Youtube-data medtages\" og fravælg alt bortset fra \"abonnementer\".
-\n4. Klik på \"Næste trin\" og derefter \"Opret eksport\".
-\n5. Klik på \"Download\" knappen efter den popper frem.
-\n6. Klik på \"IMPORTER FIL\" nederst på denne side og vælg den downloadede .zip fil.
-\n7. [Såfremt .zip-importeringen slår fejl] Uddrag .csv filen (som normalt findes i \"YouTube og YouTube Music/abonnementer/abonnementer.csv\"). Klik på \"IMPORTER FIL\" nederst på denne side, og vælg den uddragede .csv fil</string>
+\n2. Log ind, når du bliver bedt om det
+\n3. Klik på \"Alle Youtube-data medtages\", og fravælg alt bortset fra \"abonnementer\".
+\n4. Klik på \"Næste trin\", og derefter \"Opret eksport\".
+\n5. Klik på \"Download\"-knappen efter den popper frem.
+\n6. Klik på \"IMPORTÉR FIL\" nederst på denne side, og vælg den hentede .zip-fil.
+\n7. [Såfremt .zip-importeringen slår fejl] Uddrag .csv-filen (som normalt findes i \"YouTube og YouTube Music/abonnementer/abonnementer.csv\"). Klik på \"IMPORTÉR FIL\" nederst på denne side, og vælg den uddragede .csv-fil</string>
     <string name="import_soundcloud_instructions_hint">ditID, soundcloud.com/ditID</string>
-    <string name="import_network_expensive_warning">Bemærk at denne operation kan kræve meget netværkstrafik.
+    <string name="import_network_expensive_warning">Bemærk, at denne operation kan kræve meget netværkstrafik.
 \n
-\nVil du fortsætte\?</string>
-    <string name="playback_speed_control">Knapper for afspilningshastighed</string>
+\nVil du fortsætte?</string>
+    <string name="playback_speed_control">Knapper for Afspilningshastighed</string>
     <string name="playback_tempo">Tempo</string>
     <string name="playback_pitch">Tonehøjde</string>
     <string name="skip_silence_checkbox">Spol forbi stilhed</string>
     <string name="playback_step">Skridt</string>
     <string name="updates_setting_description">Vis en notifikation for at foreslå opdatering af appen, når en ny version er tilgængelig</string>
-    <string name="minimize_on_exit_title">Minimer ved appskift</string>
-    <string name="minimize_on_exit_summary">Handling når der skiftes til en anden app fra hovedvideoafspilleren — %s</string>
+    <string name="minimize_on_exit_title">Minimér ved appskift</string>
+    <string name="minimize_on_exit_summary">Handling ved skift til anden app fra hovedvideoafspiller — %s</string>
     <string name="minimize_on_exit_none_description">Ingen</string>
-    <string name="minimize_on_exit_background_description">Minimer til baggrundsafspiller</string>
-    <string name="minimize_on_exit_popup_description">Minimer til pop op-afspiller</string>
+    <string name="minimize_on_exit_background_description">Minimér til baggrundsafspiller</string>
+    <string name="minimize_on_exit_popup_description">Minimér til popup-afspiller</string>
     <string name="app_update_available_notification_title">En NewPipe-opdatering er tilgængelig!</string>
     <string name="paused">sat på pause</string>
     <string name="queued">sat i kø</string>
-    <string name="max_retry_msg">Maksimalt antal genforsøg</string>
-    <string name="max_retry_desc">Maksimalt antal forsøg før downloaden opgives</string>
+    <string name="max_retry_msg">Maks. antal genforsøg</string>
+    <string name="max_retry_desc">Maks. antal forsøg, før downloaden annulleres</string>
     <string name="pause_downloads_on_mobile">Afbryd på forbrugsafregnede netværk</string>
     <string name="pause_downloads_on_mobile_desc">Nyttigt ved skift til mobildata, selv om nogle downloads ikke kan sættes på pause</string>
     <string name="peertube_instance_add_https_only">Kun HTTPS-URL\'er understøttes</string>
@@ -410,7 +410,7 @@
     <string name="dont_show">Vis ikke</string>
     <string name="notification_action_shuffle">Bland</string>
     <string name="show_description_title">Vis beskrivelse</string>
-    <string name="open_website_license">Åbn hjemmeside</string>
+    <string name="open_website_license">Åbn webside</string>
     <string name="metadata_language">Sprog</string>
     <string name="low_quality_smaller">Lav kvalitet (mindre)</string>
     <string name="autoplay_summary">Start afspilning automatisk — %s</string>
@@ -424,10 +424,10 @@
         <item quantity="one">%1$s download slettet</item>
         <item quantity="other">%1$s downloads slettet</item>
     </plurals>
-    <string name="delete_downloaded_files_confirm">Slet alle downloadede filer fra drevet\?</string>
+    <string name="delete_downloaded_files_confirm">Slet alle hentede filer fra drevet?</string>
     <string name="pause_downloads">Sæt downloads på pause</string>
     <string name="start_main_player_fullscreen_title">Start hovedafspiller i fuld skærm</string>
-    <string name="no_dir_yet">Downloadmappe endnu ikke valgt. Vælg standardmappen nu</string>
+    <string name="no_dir_yet">Lagringsmappe endnu ikke valgt; vælg standardmappen nu</string>
     <string name="auto_queue_toggle">Sæt automatisk i kø</string>
     <string name="settings_category_player_notification_summary">Konfigurér notifikation om igangværende stream</string>
     <string name="show_age_restricted_content_summary">Vis indhold, der muligvis er uegnet for børn, fordi det har en aldersgrænse (f.eks. 18+)</string>
@@ -441,15 +441,15 @@
     <string name="recaptcha_cookies_cleared">reCAPTCHA-cookies blev ryddet</string>
     <string name="delete_playback_states_alert">Slet alle afspilningspositioner\?</string>
     <string name="missing_file">Filen er flyttet eller slettet</string>
-    <string name="error_report_notification_title">NewPipe stødte ind i en fejl, tryk for at rapportere</string>
-    <string name="error_report_open_issue_button_text">Rapporter på GitHub</string>
+    <string name="error_report_notification_title">NewPipe stødte ind i en fejl; tryk for at rapportere</string>
+    <string name="error_report_open_issue_button_text">Rapportér på GitHub</string>
     <string name="high_quality_larger">Høj kvalitet (større)</string>
     <string name="enable_queue_limit">Begræns downloadkøen</string>
     <string name="clear_cookie_summary">Ryd de cookies, som NewPipe opbevarer, når du løser en reCAPTCHA</string>
     <string name="notification_colorize_title">Farvelæg notifikation</string>
     <string name="settings_category_player_notification_title">Afspiller-notifikation</string>
-    <string name="error_report_notification_toast">En fejl opstod, se notifikationen</string>
-    <string name="show_description_summary">Slå fra for at skjule videobeskrivelsen og yderligere information</string>
+    <string name="error_report_notification_toast">En fejl opstod; se notifikationen</string>
+    <string name="show_description_summary">Slå fra for at skjule videobeskrivelse og yderligere information</string>
     <string name="show_meta_info_summary">Slå fra for at skjule metainfo-bokse med yderligere information om streamskaberen, streamindhold eller en søgeforespørgsel</string>
     <plurals name="download_finished_notification">
         <item quantity="one">Download fuldført</item>
@@ -462,31 +462,31 @@
     <string name="remote_search_suggestions">Forslag til fjernsøgning</string>
     <string name="start_main_player_fullscreen_summary">Start ikke videoer i miniafspilleren, men skift direkte til fuldskærmstilstand, hvis automatisk rotation er låst. Du kan stadig få adgang til miniafspilleren ved at forlade fuldskærm</string>
     <string name="unsupported_url_dialog_message">Kunne ikke genkende URL. Åbn med en anden app?</string>
-    <string name="hash_channel_name">Videohashfunktion notifikation</string>
-    <string name="hash_channel_description">Notifikationer om videohashfunktioners status</string>
+    <string name="hash_channel_name">Videohash-notifikation</string>
+    <string name="hash_channel_description">Notifikationer om videohashing fremskridt</string>
     <string name="error_report_channel_name">Fejlrapport-notifikation</string>
     <string name="error_report_channel_description">Notifikationer for at rapportere fejl</string>
     <string name="clear_playback_states_title">Slet afspilningspositioner</string>
     <string name="clear_playback_states_summary">Sletter alle afspilningspositioner</string>
-    <string name="downloads_storage_ask_title">Spørg hvor filen skal downloades</string>
-    <string name="enable_queue_limit_desc">Et download ad gangen</string>
-    <string name="delete_downloaded_files">Slet downloadede filer</string>
-    <string name="confirm_prompt">Vil du rydde din download historik eller slette alle downloadede filer\?</string>
+    <string name="downloads_storage_ask_title">Spørg, hvor filen skal hentes</string>
+    <string name="enable_queue_limit_desc">Ét download ad gangen</string>
+    <string name="delete_downloaded_files">Slet hentede filer</string>
+    <string name="confirm_prompt">Vil du rydde din downloadhistorik eller slette alle hentede filer?</string>
     <string name="error_download_resource_gone">Kan ikke gendanne dette download</string>
-    <string name="clear_download_history">Ryd download historik</string>
-    <string name="privacy_policy_encouragement">NewPipe projektet tager dit privatliv seriøst. Derfor samler appen intet data uden dit samtykke.
-\nNewPipes fortrolighedspolitik forklarer i detaljer, hvilke data der bliver sendt og opbevaret når du sender en nedbrudsrapport.</string>
-    <string name="copy_for_github">Kopier en formatteret rapport</string>
+    <string name="clear_download_history">Ryd downloadhistorik</string>
+    <string name="privacy_policy_encouragement">NewPipe-projektet tager dit privatliv meget alvorligt. Derfor indsamler appen ikke nogen data uden dit samtykke.
+\nNewPipes privatlivspolitik forklarer i detaljer, hvilke data der sendes og gemmes, når du sender en nedbrudsrapport.</string>
+    <string name="copy_for_github">Kopiér formateret rapport</string>
     <string name="permission_display_over_apps">Giv tilladelse til at vise over andre apps</string>
     <string name="enable_playback_state_lists_summary">Vis indikatorer for afspilningsposition i lister</string>
-    <string name="watch_history_states_deleted">Afspilningspositioner blev slettet</string>
+    <string name="watch_history_states_deleted">Afspilningspositioner slettet</string>
     <string name="clear_cookie_title">Ryd reCAPTCHA-cookies</string>
     <string name="download_already_pending">Der er en afventende download med dette navn</string>
     <string name="start_downloads">Start downloads</string>
     <string name="notification_scale_to_square_image_title">Beskær miniaturebillede til 1:1 format</string>
     <string name="notification_scale_to_square_image_summary">Beskær video-miniaturebillede i notifikationen fra 16:9 til 1:1 format</string>
     <string name="notification_actions_summary">Redigér hver underretningshandling nedenfor ved at trykke på dem. Vælg op til tre af dem, som bliver vist i den lille notifikation via afkrydsningsfelterne til højre.</string>
-    <string name="notification_actions_at_most_three">Du kan kun vælge op til tre handlinger som kan vises i den lille notifikation!</string>
+    <string name="notification_actions_at_most_three">Du kan kun vælge op til tre handlinger, der kan vises i den lille notifikation!</string>
     <string name="notification_action_buffering">Buffering</string>
     <string name="notification_colorize_summary">Få Android til at vælge notifikationens farve ud fra den primære farve i miniaturebilledet (virker ikke på alle enheder)</string>
     <string name="night_theme_title">Nat-tema</string>
@@ -495,67 +495,67 @@
 \nPga. nye YouTube-politikker om aldersbegrænsede videoer har NewPipe ikke adgang til nogen af dens videostreams og kan derfor ikke afspille dem.</string>
     <string name="crash_the_player">Crash afspilleren</string>
     <string name="clear_queue_confirmation_title">Spørg om bekræftelse, før du rydder en kø</string>
-    <string name="seekbar_preview_thumbnail_title">Forhåndsvisning af miniaturebilleder på statuslinjen</string>
+    <string name="seekbar_preview_thumbnail_title">Forhåndsvisning af miniaturebilleder på statuslinje</string>
     <string name="enqueue_next_stream">Sæt i kø som næste</string>
-    <string name="enqueued_next">Er sat som næste i køen</string>
+    <string name="enqueued_next">Sat i kø som næste</string>
     <string name="download_has_started">Download er begyndt</string>
     <string name="show_thumbnail_summary">Vis miniaturebilleder på både låseskærmen og notifikationer</string>
     <string name="recent">Nylige</string>
     <string name="notifications_disabled">Notifikationer er slået fra</string>
     <string name="comments_tab_description">Kommentarer</string>
-    <string name="related_items_tab_description">Relaterede objekter</string>
+    <string name="related_items_tab_description">Relaterede emner</string>
     <string name="main_page_content_swipe_remove">Stryg på elementer for at fjerne dem</string>
-    <string name="select_a_playlist">Vælg en spilleliste</string>
-    <string name="no_playlist_bookmarked_yet">Ingen spilleliste-bogmærker endnu</string>
-    <string name="localization_changes_requires_app_restart">Sproget ændres når appen genstarter</string>
-    <string name="title_activity_play_queue">Spillekø</string>
+    <string name="select_a_playlist">Vælg en playliste</string>
+    <string name="no_playlist_bookmarked_yet">Ingen playliste-bogmærker endnu</string>
+    <string name="localization_changes_requires_app_restart">Sproget ændres, når appen genstarter</string>
+    <string name="title_activity_play_queue">Afspillerkø</string>
     <string name="show_channel_details">Vis kanalens detaljer</string>
     <string name="enqueue_stream">Sæt i kø</string>
     <string name="enqueued">Sat i kø</string>
-    <string name="loading_stream_details">Loader streamets detaljer…</string>
-    <string name="processing_may_take_a_moment">Processere... Det kan tage et øjeblik</string>
-    <string name="show_memory_leaks">Vis hukommelsestab</string>
-    <string name="disable_media_tunneling_title">Deaktiver medietunneler</string>
+    <string name="loading_stream_details">Indlæser streamdetaljer…</string>
+    <string name="processing_may_take_a_moment">Behandler… Det kan tage et øjeblik</string>
+    <string name="show_memory_leaks">Vis hukommelseslækager</string>
+    <string name="disable_media_tunneling_title">Deaktivér medietunneling</string>
     <string name="show_image_indicators_title">Vis billedindikatorer</string>
     <string name="streams_notifications_network_title">Netværkskrav</string>
     <string name="any_network">Alle netværk</string>
     <string name="streams_notifications_interval_title">Kontrolfrekvens</string>
     <string name="enable_streams_notifications_title">Notifikationer ved nye streams</string>
-    <string name="enable_streams_notifications_summary">Giv besked om nye streams fra abonnementer</string>
+    <string name="enable_streams_notifications_summary">Underret om nye streams fra abonnementer</string>
     <string name="manual_update_description">Tjek manuelt efter opdateringer</string>
     <string name="checking_updates_toast">Tjekker efter opdateringer…</string>
-    <string name="recovering">Gendanner</string>
-    <string name="feed_load_error_fast_unknown">\"Hurtig feed\"-tilstand viser ikke mere information om dette.</string>
+    <string name="recovering">gendanner</string>
+    <string name="feed_load_error_fast_unknown">\"Hurtig feed\"-tilstand oplyser ikke mere info om dette.</string>
     <string name="manual_update_title">Tjek efter opdateringer</string>
     <string name="remove_watched_popup_title">Fjern sete videoer\?</string>
-    <string name="disable_media_tunneling_summary">Deaktiver medietunneler hvis du oplever en sort skærm eller hak ved videoafspilning</string>
-    <string name="error_report_open_github_notice">Venligst tjek om der allerede eksisterer en problemrapport som diskuterer dit crash. Hvis du opretter duplikatrapporter, tager du tid fra os som vi kunne bruge på at fikse fejlen.</string>
+    <string name="disable_media_tunneling_summary">Deaktivér medietunneling, hvis du oplever en sort skærm eller hakken ved videoafspilning.</string>
+    <string name="error_report_open_github_notice">Tjek venligst, om der allerede findes et problem, der diskuterer dit nedbrud. Når du opretter flere tickets, tager du tid fra os, som vi kunne bruge på at løse den faktiske fejl.</string>
     <string name="check_new_streams">Tjek efter nye streams</string>
     <string name="create_error_notification">Lav en fejlnotifikation</string>
     <string name="local">Lokale</string>
-    <string name="feed_load_error_terminated">Udgiverens bruger er blevet slettet.
-\nNewpipe kan ikke indlæse dette feed i fremtiden.
-\nVil du fjerne dit abonnement på denne kanal\?</string>
-    <string name="feed_oldest_subscription_update">Feedet blev sidst opdateret for %s</string>
+    <string name="feed_load_error_terminated">Udgiverens konto er blevet lukket.
+\nNewPipe vil ikke være i stand til at indlæse dette feed i fremtiden.
+\nØnsker du at afmelde denne kanal?</string>
+    <string name="feed_oldest_subscription_update">Feed sidst opdateret: %s</string>
     <string name="feed_subscription_not_loaded_count">Ikke indlæst: %d</string>
     <string name="feed_notification_loading">Indlæser feed…</string>
     <string name="feed_new_items">Nye feed elementer</string>
-    <string name="feed_update_threshold_summary">Tid siden sidste opdatering for at et abonnoment bliver forældet - %s</string>
-    <string name="feed_update_threshold_option_always_update">Altid opdater</string>
+    <string name="feed_update_threshold_summary">Tid efter sidste opdatering, før et abonnement betragtes som forældet — %s</string>
+    <string name="feed_update_threshold_option_always_update">Altid opdatér</string>
     <string name="feed_group_dialog_select_subscriptions">Vælg abonnementer</string>
     <string name="georestricted_content">Dette indhold er ikke tilgængeligt i dit land.</string>
     <string name="video_detail_by">Af %s</string>
-    <string name="remove_watched_popup_warning">Videoer, der er blevet set før og efter, at de er blevet tilføjet til spillelisten, vil blive fjernet.
-\nEr du sikker\? Dette kan ikke gøres om!</string>
+    <string name="remove_watched_popup_warning">Videoer, der er blevet set før og efter tilføjelse til playlisten, vil blive fjernet.
+\nEr du sikker? Dette kan ikke fortrydes!</string>
     <string name="show_thumbnail_title">Vis miniaturebillede</string>
     <string name="metadata_tags">Tags</string>
     <string name="metadata_age_limit">Aldersbegrænsning</string>
-    <string name="content_not_supported">Dette indhold er ikke understøttet af NewPipe.
+    <string name="content_not_supported">Dette indhold understøttes endnu ikke af NewPipe.
 \n
 \nVi håber at kunne understøtte det i en fremtiden.</string>
-    <string name="paid_content">Dette indhold er kun tilgængeligt for brugere som har betalt for det. Det kan ikke blive streamet eller downloadet af NewPipe.</string>
-    <string name="account_terminated">Bruger slettet</string>
-    <string name="private_content">Dette indhold er privat, så det jan ikke blive streamet eller downloadet af NewPipe.</string>
+    <string name="paid_content">Dette indhold er kun tilgængeligt for brugere, som har betalt for det, så det kan ikke streames eller downloades af NewPipe.</string>
+    <string name="account_terminated">Konto lukket</string>
+    <string name="private_content">Dette indhold er privat, så det kan ikke streames eller downloades af NewPipe.</string>
     <string name="recently_added">Nyligt tilføjede</string>
     <string name="featured">Fremhævede</string>
     <string name="service_provides_reason">%s giver denne grund:</string>
@@ -570,10 +570,10 @@
     <string name="feed_group_dialog_delete_message">Vil du slette denne gruppe\?</string>
     <string name="metadata_licence">Licens</string>
     <plurals name="new_streams">
-        <item quantity="one">%s nyt stream</item>
+        <item quantity="one">%s ny stream</item>
         <item quantity="other">%s nye streams</item>
     </plurals>
-    <string name="semitone">Semitone</string>
+    <string name="semitone">Halvtone</string>
     <plurals name="hours">
         <item quantity="one">%d time</item>
         <item quantity="other">%d timer</item>
@@ -583,16 +583,16 @@
         <item quantity="other">%d dage</item>
     </plurals>
     <string name="channel_created_by">Lavet af %s</string>
-    <string name="feed_use_dedicated_fetch_method_disable_button">Slå hurtigtilstand fra</string>
-    <string name="feed_use_dedicated_fetch_method_enable_button">Slå hurtigtilstand til</string>
-    <string name="feed_use_dedicated_fetch_method_title">Hent fra det dedikerede feed når det er muligt</string>
+    <string name="feed_use_dedicated_fetch_method_disable_button">Deaktivér hurtig-tilstand</string>
+    <string name="feed_use_dedicated_fetch_method_enable_button">Aktivér hurtig-tilstand</string>
+    <string name="feed_use_dedicated_fetch_method_title">Hent fra dedikeret feed, når det er tilgængeligt</string>
     <string name="feed_update_threshold_title">Feed opdateringsgrænse</string>
     <string name="settings_category_feed_title">Feed</string>
     <plurals name="feed_group_dialog_selection_count">
         <item quantity="one">%d valgt</item>
         <item quantity="other">%d valgte</item>
     </plurals>
-    <string name="feed_processing_message">Processerer feed…</string>
+    <string name="feed_processing_message">Behandler feed…</string>
     <string name="feed_groups_header_title">Kanalgrupper</string>
     <plurals name="minutes">
         <item quantity="one">%d minut</item>
@@ -601,19 +601,19 @@
     <string name="remove_watched">Fjern sete</string>
     <string name="choose_instance_prompt">Vælg en instans</string>
     <string name="error_timeout">Forbindelse afbrudt</string>
-    <string name="error_progress_lost">Fremskridt tabt fordi filen blev slettet</string>
+    <string name="error_progress_lost">Fremskridt tabt, fordi filen blev slettet</string>
     <string name="error_postprocessing_stopped">NewPipe blev lukket under arbejde på filen</string>
-    <string name="overwrite_failed">Kan ikke overskrive filen</string>
-    <string name="start_accept_privacy_policy">For at være i overenstemmelse med GDPR fanger vi din opmærksomhed hentil NewPipes privatpolitik. Venligst læs den med omhu.
-\nDu skal acceptere den for at sende os en fejlrapport.</string>
+    <string name="overwrite_failed">kan ikke overskrive filen</string>
+    <string name="start_accept_privacy_policy">For at overholde den europæiske databeskyttelsesforordning (GDPR) henviser vi hermed din opmærksomhed til NewPipes privatlivspolitik. Læs den venligst omhyggeligt.
+\nDu skal acceptere den for at kunne sende os fejlrapporten.</string>
     <string name="unhook_checkbox">Aflænk (kan skabe forvrængning)</string>
-    <string name="import_soundcloud_instructions">Importer en SoundCloud profil ved at skrive enten dit URL eller ID:
+    <string name="import_soundcloud_instructions">Importér en SoundCloud-profil ved at skrive enten dit URL eller ID:
 \n
-\n1. Slå \"desktop-version\" til i mobilbrowsere.
+\n1. Slå \"desktop-tilstand\" til i mobilbrowsere.
 \n2. Gå til denne adresse: %1$s
-\n3. Log ind når du bliver spurgt
-\n4. Kopier adressen på den profil du bliver henstillet til.</string>
-    <string name="show_original_time_ago_title">Vis den oprindelige tidsforskel på elementer</string>
+\n3. Log ind, når du bliver bedt om det
+\n4. Kopiér adressen på den profil, du bliver omdirigeret til.</string>
+    <string name="show_original_time_ago_title">Vis oprindelig tid siden på elementer</string>
     <string name="playlist_no_uploader">Autogenereret (ingen uploader fundet)</string>
     <string name="unmute">Slå lyd til</string>
     <string name="mute">Sæt på lydløs</string>
@@ -621,7 +621,7 @@
     <string name="error_unable_to_load_comments">Kunne ikke indlæse kommentarer</string>
     <string name="default_kiosk_page_summary">Standard Kiosk</string>
     <string name="done">Færdig</string>
-    <string name="subtitle_activity_recaptcha">Tryk på \"Færdig\" når den er løst</string>
+    <string name="subtitle_activity_recaptcha">Tryk på \"Færdig\", når den er løst</string>
     <string name="no_comments">Ingen kommentarer</string>
     <string name="infinite_videos">∞ videoer</string>
     <string name="no_one_listening">Ingen lyttere</string>
@@ -632,26 +632,26 @@
     <string name="no_one_watching">Ingen seere</string>
     <string name="drawer_header_description">Skift service, nuværende valg:</string>
     <string name="comments_are_disabled">Kommentarer er slået fra</string>
-    <string name="no_app_to_open_intent">Ingen apps på din enhed kan åbne dette</string>
+    <string name="no_app_to_open_intent">Ingen app på din enhed kan åbne dette</string>
     <string name="error_insufficient_storage_left">Ingen ledig plads på enheden</string>
-    <string name="app_language_title">App sprog</string>
+    <string name="app_language_title">App-sprog</string>
     <string name="remove_watched_popup_yes_and_partially_watched_videos">Ja, og delvist sete videoer</string>
     <string name="feed_load_error">Fejl ved indlæsning af feed</string>
     <string name="feed_load_error_account_info">Kunne ikke indlæse feed for \'%s\'.</string>
-    <string name="show_crash_the_player_title">Vis \"crash afspilleren\"</string>
+    <string name="show_crash_the_player_title">Vis \"Crash afspilleren\"</string>
     <string name="crash_the_app">Crash appen</string>
-    <string name="show_crash_the_player_summary">Vis et crash alternativ når afspilleren er i brug</string>
+    <string name="show_crash_the_player_summary">Vis en crash mulighed, når afspilleren er i brug</string>
     <string name="show_error_snackbar">Vis en fejl snackbar</string>
-    <string name="downloads_storage_use_saf_title">Brug system mappevælger (SAF)</string>
+    <string name="downloads_storage_use_saf_title">Brug systemets mappevælger (SAF)</string>
     <string name="detail_sub_channel_thumbnail_view_description">Kanalens avatar-miniaturebillede</string>
     <string name="soundcloud_go_plus_content">Dette er et SoundCloud Go+-nummer, i hvert fald i dit land, så det kan ikke streames eller downloades af NewPipe.</string>
     <string name="no_appropriate_file_manager_message_android_10">Der blev ikke fundet nogen passende filhåndtering til denne handling.
-\nInstaller en Storage Access Framework-kompatibel filhåndtering</string>
+\nInstallér venligst en Storage Access Framework-kompatibel filhåndtering</string>
     <string name="no_appropriate_file_manager_message">Der blev ikke fundet nogen passende filhåndtering til denne handling.
-\nInstaller et filhåndteringsprogram eller prøv at deaktivere \'%s\' i download-indstillingerne</string>
+\nInstallér venligst en filhåndtering, eller prøv at deaktivere \'%s\' i downloadindstillingerne</string>
     <string name="description_select_enable">Aktivér valg af tekst i beskrivelsen</string>
     <string name="auto_device_theme_title">Automatisk (enhedstema)</string>
-    <string name="description_select_disable">Deaktiver valg af tekst i beskrivelsen</string>
+    <string name="description_select_disable">Deaktivér valg af tekst i beskrivelsen</string>
     <string name="detail_pinned_comment_view_description">Fastgjort kommentar</string>
     <string name="you_successfully_subscribed">Du abonnerer nu på denne kanal</string>
     <string name="enumeration_comma">,</string>
@@ -660,50 +660,50 @@
     <string name="feed_use_dedicated_fetch_method_summary">Den er tilgængelig i nogle tjenester og er normalt meget hurtigere, men kan returnere et begrænset antal elementer og ofte ufuldstændige oplysninger (f.eks. ingen varighed, elementtype, ingen live-status)</string>
     <string name="unknown_format">Ukendt format</string>
     <string name="unknown_quality">Ukendt kvalitet</string>
-    <string name="detail_heart_img_view_description">Hjertemarkeret af indholdsskaberen</string>
+    <string name="detail_heart_img_view_description">Hjertemarkeret af skaber</string>
     <string name="progressive_load_interval_title">Størrelse på afspilningsinterval</string>
     <string name="progressive_load_interval_exoplayer_default">ExoPlayer-standard</string>
     <string name="feed_group_dialog_empty_name">Tomt gruppenavn</string>
     <string name="downloads_storage_ask_summary">Du vil blive spurgt, hvor du vil gemme hver enkelt download.
-\nAktiver systemet mappevælger (SAF), hvis du vil downloade til et eksternt SD-kort</string>
-    <string name="show_original_time_ago_summary">Originaltekster fra tjenester vil være synlige i stream-emner</string>
+\nAktivér systemets mappevælger (SAF), hvis du vil hente til et eksternt SD-kort</string>
+    <string name="show_original_time_ago_summary">Originaltekster fra tjenester vil være synlige i stream-elementer</string>
     <string name="no_video_streams_available_for_external_players">Ingen videostreams er tilgængelige for eksterne afspillere</string>
     <string name="off">Fra</string>
     <string name="tablet_mode_title">Tablet-tilstand</string>
-    <string name="youtube_music_premium_content">Denne video er kun tilgængelig for YouTube Music Premium-medlemmer, så den kan ikke streames eller downloades af NewPipe.</string>
-    <string name="downloads_storage_use_saf_summary">\"Storage Access Framework\" gør det muligt at downloade til et eksternt SD-kort</string>
-    <string name="enable_disposed_exceptions_summary">Fremtving indberetning af ikke-leverbare Rx-undtagelser uden for fragmentets eller aktivitetens livscyklus efter bortskaffelse</string>
-    <string name="app_update_available_notification_text">Tryk for at downloade %s</string>
+    <string name="youtube_music_premium_content">Denne video er kun tilgængelig for YouTube Musik Premium-medlemmer, så den kan ikke streames eller downloades af NewPipe.</string>
+    <string name="downloads_storage_use_saf_summary">\"Storage Access Framework\" gør det muligt at hente til et eksternt SD-kort</string>
+    <string name="enable_disposed_exceptions_summary">Tving rapportering af ikke-levérbare Rx-undtagelser uden for fragmentets eller aktivitetens livscyklus efter bortskaffelse</string>
+    <string name="app_update_available_notification_text">Tryk for at hente %s</string>
     <string name="downloads_storage_use_saf_summary_api_29">Fra og med Android 10 understøttes kun \"Storage Access Framework\"</string>
-    <string name="feed_use_dedicated_fetch_method_help_text">Synes du, at feed-indlæsning er for langsom\? Hvis det er tilfældet, så prøv at aktivere hurtig indlæsning (du kan ændre det i indstillingerne eller ved at trykke på knappen nedenfor).
+    <string name="feed_use_dedicated_fetch_method_help_text">Synes du, at feed-indlæsning er for langsom? Hvis det er tilfældet, så prøv at aktivere hurtig indlæsning (du kan ændre det i indstillingerne eller ved at trykke på knappen nedenfor).
 \n
 \nNewPipe tilbyder to strategier til feed-indlæsning:
 \n- Hentning af hele abonnementskanalen, hvilket er langsomt, men komplet.
 \n- Brug af et dedikeret service endpoint, hvilket er hurtigt, men normalt ikke komplet.
 \n
-\nForskellen mellem de to er, at den hurtige metode normalt mangler nogle oplysninger, f.eks. elementets varighed eller type (kan ikke skelne mellem livevideoer og normale videoer), og den returnerer muligvis færre elementer.
+\nForskellen mellem de to, er, at den hurtige metode normalt mangler nogle oplysninger, f.eks. elementets varighed eller type (kan ikke skelne mellem livevideoer og normale videoer), og den returnerer muligvis færre elementer.
 \n
 \nYouTube er et eksempel på en tjeneste, der tilbyder denne hurtige metode med sit RSS-feed.
 \n
 \nValget er altså et spørgsmål om, hvad du foretrækker: hastighed eller præcise oplysninger.</string>
-    <string name="selected_stream_external_player_not_supported">Den valgte stream er ikke understøttet af eksterne afspillere</string>
+    <string name="selected_stream_external_player_not_supported">Den valgte stream understøttes ikke af eksterne afspillere</string>
     <string name="night_theme_available">Denne indstilling er kun tilgængelig, hvis %s er valgt som tema</string>
-    <string name="description_select_note">Du kan nu vælge tekst i beskrivelsen. Bemærk, at siden kan flimre, og at links muligvis ikke kan klikkes på, mens du er i valgtilstand.</string>
+    <string name="description_select_note">Du kan nu vælge tekst inde i beskrivelsen. Bemærk, at siden kan flimre, og at links muligvis ikke kan klikkes på, mens du er i markeringstilstand.</string>
     <string name="streams_not_yet_supported_removed">Streams, som endnu ikke understøttes af downloaderen, vises ikke</string>
     <string name="fast_mode">Hurtig tilstand</string>
     <string name="import_subscriptions_hint">Importér eller eksportér abonnementer fra 3-punktsmenuen</string>
     <string name="faq_title">Ofte stillede spørgsmål</string>
     <string name="faq_description">Hvis du har problemer med at bruge appen, bør du tjekke disse svar på almindelige spørgsmål!</string>
-    <string name="faq">Se på hjemmeside</string>
+    <string name="faq">Se på webside</string>
     <string name="show_image_indicators_summary">Vis Picasso-farvede bånd oven på billeder, der angiver deres kilde: rød for netværk, blå for disk og grøn for hukommelse</string>
     <string name="app_update_unavailable_toast">Du kører den nyeste version af NewPipe</string>
-    <string name="new_seek_duration_toast">På grund af ExoPlayer-begrænsninger blev søgetiden sat til %d sekunder</string>
+    <string name="new_seek_duration_toast">Pga. ExoPlayer-begrænsninger blev søgevarigheden sat til %d sekunder</string>
     <string name="feed_group_show_only_ungrouped_subscriptions">Vis kun ikke-grupperede abonnementer</string>
-    <string name="playlist_page_summary">Side med spillelister</string>
+    <string name="playlist_page_summary">Playlisteside</string>
     <string name="select_night_theme_toast">Du kan vælge dit foretrukne nattema nedenfor</string>
-    <string name="night_theme_summary">Vælg dit foretrukne nattema - %s</string>
+    <string name="night_theme_summary">Vælg dit foretrukne nattema — %s</string>
     <string name="metadata_support">Support</string>
-    <string name="metadata_host">Host</string>
+    <string name="metadata_host">Vært</string>
     <string name="metadata_privacy_public">Offentlig</string>
     <string name="metadata_privacy_unlisted">Ikke oplyst</string>
     <string name="metadata_privacy_private">Privat</string>
@@ -715,14 +715,14 @@
     <string name="sort">Sortér</string>
     <string name="ignore_hardware_media_buttons_title">Ignorér hardware medie-knap begivenheder</string>
     <string name="ignore_hardware_media_buttons_summary">Brugbart f.eks. hvis du bruger et headset med ødelagte fysiske knapper</string>
-    <string name="duplicate_in_playlist">Playlists der er grået ud, indeholder allerede dette objekt.</string>
-    <string name="unset_playlist_thumbnail">Inaktiver permanent thumbnail</string>
-    <string name="msg_failed_to_copy">Fejlede at kopiere til udklipsholderen</string>
+    <string name="duplicate_in_playlist">Playlisterne, der er nedtonede, indeholder allerede dette element.</string>
+    <string name="unset_playlist_thumbnail">Fjern permanent miniaturebillede</string>
+    <string name="msg_failed_to_copy">Kunne ikke kopiere til udklipsholder</string>
     <string name="prefer_original_audio_summary">Brug det originale lydspor uanset sprog</string>
     <string name="prefer_descriptive_audio_title">Foretræk lydbeskrivelser</string>
     <string name="prefer_original_audio_title">Foretræk original lyd</string>
-    <string name="prefer_descriptive_audio_summary">Brug lydbeskrivelser for personer med nedsat syn, hvis tilgængeligt</string>
-    <string name="notification_actions_summary_android13">Redigér hver underretningshandling nedenfor ved at trykke på dem. De første tre handlinger (afspil/sæt på pause, forrige og næste) er indstillet af systemet og kan ikke brugerdefineres.</string>
+    <string name="prefer_descriptive_audio_summary">Hvis tilgængeligt, brug lydbeskrivelser for personer med nedsat syn</string>
+    <string name="notification_actions_summary_android13">Redigér hver underretningshandling nedenfor ved at trykke på dem. De første tre handlinger (afspil/sæt på pause, forrige og næste) er indstillet af systemet og kan ikke tilpasses.</string>
     <string name="loading_metadata_title">Indlæser Metadata…</string>
     <string name="remove_duplicates_title">Fjern duplikater?</string>
     <string name="image_quality_summary">Vælg kvaliteten af billeder, og om billeder overhovedet skal indlæses, for at reducere data- og hukommelsesforbrug. Ændringer rydder både billedcachen i hukommelsen og på disken — %s</string>
@@ -743,7 +743,7 @@
     <string name="remove_duplicates">Fjern duplikater</string>
     <string name="feed_hide_streams_title">Vis følgende streams</string>
     <string name="feed_fetch_channel_tabs">Hent kanal-faner</string>
-    <string name="feed_fetch_channel_tabs_summary">Faner, der skal hentes, når feedet opdateres. Denne indstilling har ingen effekt, hvis en kanal opdateres i hurtig tilstand.</string>
+    <string name="feed_fetch_channel_tabs_summary">Faner, der skal hentes, når feedet opdateres. Denne indstilling har ingen effekt, hvis en kanal opdateres i hurtig-tilstand.</string>
     <string name="metadata_thumbnails">Miniaturebilleder</string>
     <string name="select_audio_track_external_players">Vælg lydspor til eksterne afspillere</string>
     <string name="unknown_audio_track">Ukendt</string>
@@ -771,8 +771,8 @@
     <string name="image_quality_title">Billedkvalitet</string>
     <string name="image_quality_none">Indlæs ikke billeder</string>
     <string name="image_quality_low">Lav kvalitet</string>
-    <string name="share_playlist">Del playliste</string>
-    <string name="share_playlist_with_titles_message">Del playliste med detajler såsom playliste navn og videotitler eller som en simpel liste over video-URL\'er</string>
+    <string name="share_playlist">Del Playliste</string>
+    <string name="share_playlist_with_titles_message">Del playliste med detajler såsom playlistenavn og videotitler eller som en simpel liste over video-URL\'er</string>
     <string name="share_playlist_with_titles">Del med Titler</string>
     <string name="share_playlist_with_list">Del URL-liste</string>
     <plurals name="replies">
@@ -785,4 +785,32 @@
     <string name="remove_duplicates_message">Ønsker du at fjerne alle duplikerede streams i denne playliste?</string>
     <string name="forward">Spol frem</string>
     <string name="left_gesture_control_title">Venstre bevægelseshandling</string>
+    <string name="settings_category_exoplayer_title">ExoPlayer-indstillinger</string>
+    <string name="settings_category_exoplayer_summary">Administrér nogle ExoPlayer-indstillinger. Disse ændringer kræver genstart af afspilleren for at træde i kraft</string>
+    <string name="main_tabs_position_title">Position for hovedfaner</string>
+    <string name="main_tabs_position_summary">Flyt hovedfanens vælger til bunden</string>
+    <string name="playlist_add_stream_success_duplicate">Duplikat tilføjet %d gang(e)</string>
+    <string name="disable_media_tunneling_automatic_info">Medietunneling var som standard deaktiveret på din enhed, fordi din enhedsmodel er kendt for ikke at understøtte det.</string>
+    <string name="metadata_uploader_avatars">Uploader-avatars</string>
+    <string name="metadata_subchannel_avatars">Underkanal-avatars</string>
+    <string name="metadata_avatars">Avatars</string>
+    <string name="metadata_banners">Bannere</string>
+    <string name="metadata_subscribers">Abonnenter</string>
+    <string name="audio_track_present_in_video">Et lydspor bør allerede være til stede i denne stream</string>
+    <string name="use_exoplayer_decoder_fallback_title">Brug ExoPlayers fallback-funktion til dekoderen</string>
+    <string name="audio_track_type_dubbed">oversat</string>
+    <string name="audio_track_type_descriptive">beskrivende</string>
+    <string name="channel_tab_shorts">Shorts</string>
+    <string name="more_options">Flere muligheder</string>
+    <string name="question_mark">\?</string>
+    <string name="video_details_list_item">- %1$s: %2$s</string>
+    <string name="share_playlist_content_details">%1$s
+\n%2$s</string>
+    <string name="use_exoplayer_decoder_fallback_summary">Aktivér denne indstilling, hvis du oplever initialiseringsproblemer med dekodere, som falder tilbage til dekodere af lavere prioritet, hvis de primære dekoderes initialisering fejler. Dette kan resultere i dårligere afspilningsydelse end ved brug af primære dekodere</string>
+    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Denne løsning frigiver og genindstiller video-codecs, når en overflade ændres, i stedet for at indstille overfladen til codec\'en direkte. Denne indstilling bruges allerede af ExoPlayer på nogle enheder med dette problem, men har kun effekt på Android 6 og nyere.
+\n
+\nAktivering af denne indstilling kan forhindre afspilningsfejl, når du skifter den aktuelle videoafspiller eller skifter til fuld skærm</string>
+    <string name="always_use_exoplayer_set_output_surface_workaround_title">Brug altid ExoPlayers overfladeindstilling til video-output som løsning</string>
+    <string name="card">Kort</string>
+    <string name="audio_track_name">%1$s %2$s</string>
 </resources>
\ No newline at end of file

From 3134f5e747af5897ad1dce34c31f3e1b70603756 Mon Sep 17 00:00:00 2001
From: Mohammed Anas <triallax@tutanota.com>
Date: Tue, 26 Mar 2024 20:28:28 +0000
Subject: [PATCH 102/141] Don't add "question" label to question discussions
 (#10906)

They already have a "Questions" category of their own anyway.
---
 .github/DISCUSSION_TEMPLATE/questions.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml
index bb7894625..2d467d5e5 100644
--- a/.github/DISCUSSION_TEMPLATE/questions.yml
+++ b/.github/DISCUSSION_TEMPLATE/questions.yml
@@ -1,4 +1,3 @@
-labels: [question]
 body:
   - type: markdown
     attributes:

From 0d4c1bee3f140a87802d9a3b6252bae1b952dcc7 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Tue, 26 Mar 2024 22:32:29 +0100
Subject: [PATCH 103/141] [CI] Update gradle/wrapper-validation-action to v2

Updates deprecated Node 16 to 20
---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4800f8578..c3aff730b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,7 +37,7 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-      - uses: gradle/wrapper-validation-action@v1
+      - uses: gradle/wrapper-validation-action@v2
 
       - name: create and checkout branch
         # push events already checked out the branch

From c34d30dc17a4944bf3b65fc3bdb8af034fc4e092 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Tue, 26 Mar 2024 22:48:47 +0100
Subject: [PATCH 104/141] [CI] Update sonar job to use cache@v4

Updates deprecated Node 16 to 20
---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c3aff730b..0d76e1645 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -123,7 +123,7 @@ jobs:
           cache: 'gradle'
 
       - name: Cache SonarCloud packages
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ~/.sonar/cache
           key: ${{ runner.os }}-sonar

From b06a9618d4cbc4e5f7bfcb87be434e55c340488e Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 27 Mar 2024 10:18:04 +0100
Subject: [PATCH 105/141] use GitHub markdown to emphasise warning in all
 READMEs

---
 doc/README.asm.md   | 7 ++++---
 doc/README.de.md    | 7 ++++---
 doc/README.es.md    | 7 ++++---
 doc/README.fr.md    | 7 ++++---
 doc/README.hi.md    | 7 ++++---
 doc/README.it.md    | 7 ++++---
 doc/README.ja.md    | 7 ++++---
 doc/README.ko.md    | 7 ++++---
 doc/README.pa.md    | 7 ++++---
 doc/README.pl.md    | 7 ++++---
 doc/README.pt_BR.md | 7 ++++---
 doc/README.ro.md    | 7 ++++---
 doc/README.ru.md    | 7 ++++---
 doc/README.ryu.md   | 7 ++++---
 doc/README.so.md    | 7 ++++---
 doc/README.sr.md    | 7 ++++---
 doc/README.tr.md    | 7 ++++---
 doc/README.zh_TW.md | 7 ++++---
 18 files changed, 72 insertions(+), 54 deletions(-)

diff --git a/doc/README.asm.md b/doc/README.asm.md
index af855c4cd..2a7c10f51 100644
--- a/doc/README.asm.md
+++ b/doc/README.asm.md
@@ -20,9 +20,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](/README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>সতৰ্কবাণী: এই এপটো নিৰ্মিয়মান অৱস্থাত আছে, গতিকে আপুনি ব্যৱহাৰ কৰোঁতে কোনো অসুবিধাৰ সন্মুখীন হ'ব পাৰে।  যদি আপুনি অসুবিধাৰ সন্মুখীন হয়, ইছ্যু টেমপ্লেট পূৰণ কৰি আমাৰ GITHUB ৰিপ'জিটৰিত এটা ইছ্যু খোলক।</b>
-
-<b>NEWPIPE, GOOGLE PLAY ষ্ট'ৰত ৰাখিলে তেওঁলোকৰ চৰ্ত আৰু নিয়ম উলংঘা হয়</b>
+> [!warning]
+> <b>এই এপটো নিৰ্মিয়মান অৱস্থাত আছে, গতিকে আপুনি ব্যৱহাৰ কৰোঁতে কোনো অসুবিধাৰ সন্মুখীন হ'ব পাৰে।  যদি আপুনি অসুবিধাৰ সন্মুখীন হয়, ইছ্যু টেমপ্লেট পূৰণ কৰি আমাৰ GITHUB ৰিপ'জিটৰিত এটা ইছ্যু খোলক।</b>
+>
+> <b>NEWPIPE, GOOGLE PLAY ষ্ট'ৰত ৰাখিলে তেওঁলোকৰ চৰ্ত আৰু নিয়ম উলংঘা হয়</b>
 
 ## স্ত্ৰীনশ্বট
 
diff --git a/doc/README.de.md b/doc/README.de.md
index 3714020ff..e269da05c 100644
--- a/doc/README.de.md
+++ b/doc/README.de.md
@@ -21,9 +21,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>WARNUNG: DIESE APP BEFINDET SICH IN EINER BETA-PHASE, DAHER KÖNNTEST DU BUGS BEGEGNEN. FALLS DIES PASSIERT, ERSTELLE EIN ISSUE (AUF ENGLISCH) IN UNSEREM GITHUB REPOSITORY, INDEM DU DIE VORLAGE DORT AUSFÜLLST.</b>
-
-<b>NEWPIPE, ODER JEGLICHEN FORK DAVON, IM GOOGLE PLAYSTORE ANZUBIETEN, VERLETZT DESSEN GESCHÄFTSBEDINGUNGEN.</b>
+> [!warning]
+> <b>DIESE APP BEFINDET SICH IN EINER BETA-PHASE, DAHER KÖNNTEST DU BUGS BEGEGNEN. FALLS DIES PASSIERT, ERSTELLE EIN ISSUE (AUF ENGLISCH) IN UNSEREM GITHUB REPOSITORY, INDEM DU DIE VORLAGE DORT AUSFÜLLST.</b>
+>
+> <b>NEWPIPE, ODER JEGLICHEN FORK DAVON, IM GOOGLE PLAYSTORE ANZUBIETEN, VERLETZT DESSEN GESCHÄFTSBEDINGUNGEN.</b>
 
 ## Screenshots
 
diff --git a/doc/README.es.md b/doc/README.es.md
index 5aa5eb55c..b6a00e9d3 100644
--- a/doc/README.es.md
+++ b/doc/README.es.md
@@ -19,9 +19,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDES ENCONTRAR BUGS. SI ENCUENTRAS UNO ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO DE GITHUB.</b>
-
-<b>COLOCAR NEWPIPE O CUALQUIER FORK DE NEWPIPE EN LA GOOGLE PLAY STORE VIOLARÁ SUS TÉRMINOS Y CONDICIONES.</b>
+> [!warning]
+> <b>ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDES ENCONTRAR BUGS. SI ENCUENTRAS UNO ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO DE GITHUB.</b>
+>
+> <b>COLOCAR NEWPIPE O CUALQUIER FORK DE NEWPIPE EN LA GOOGLE PLAY STORE VIOLARÁ SUS TÉRMINOS Y CONDICIONES.</b>
 
 ## Capturas de Pantalla
 
diff --git a/doc/README.fr.md b/doc/README.fr.md
index b68c1e321..7d4673b69 100644
--- a/doc/README.fr.md
+++ b/doc/README.fr.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>ATTENTION: CETTE APP EST EN BETA, VOUS POUVEZ DONC RENCONTRER DES BUGS. SI C'EST LE CAS, OUVREZ UNE ISSUE DANS NOTRE DÉPÔT GITHUB EN REMPLISSANT LE MODÈLE D'ISSUE.</b>
-
-<b>METTRE NEWPIPE, OU N'IMPORTE QUEL FORK DE NEWPIPE, SUR LE GOOGLE PLAY STORE ENFREINT LEURS CONDITIONS D'UTILISATION</b>
+> [!warning]
+> <b>CETTE APP EST EN BETA, VOUS POUVEZ DONC RENCONTRER DES BUGS. SI C'EST LE CAS, OUVREZ UNE ISSUE DANS NOTRE DÉPÔT GITHUB EN REMPLISSANT LE MODÈLE D'ISSUE.</b>
+>
+> <b>METTRE NEWPIPE, OU N'IMPORTE QUEL FORK DE NEWPIPE, SUR LE GOOGLE PLAY STORE ENFREINT LEURS CONDITIONS D'UTILISATION</b>
 
 ## Captures d'écran
 
diff --git a/doc/README.hi.md b/doc/README.hi.md
index 536be4d43..282e75420 100644
--- a/doc/README.hi.md
+++ b/doc/README.hi.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>चेतावनी: यह एक बीटा संस्करण है, तो अगर आपको इसमें बग्स नज़र आते हैं, कृपया हमारे GitHub रिपॉज़िटरी के ज़रिए एक समस्या खोल दें।</b>
-
-<b>NewPipe या इसके किसी फोर्क को Google Play Store पर डालने पर Store के नियमों और शर्तों का उल्लंघन होता है।</b>
+> [!warning]
+> <b>यह एक बीटा संस्करण है, तो अगर आपको इसमें बग्स नज़र आते हैं, कृपया हमारे GitHub रिपॉज़िटरी के ज़रिए एक समस्या खोल दें।</b>
+>
+> <b>NewPipe या इसके किसी फोर्क को Google Play Store पर डालने पर Store के नियमों और शर्तों का उल्लंघन होता है।</b>
 
 ## ऐप कैसी दिखती है
 
diff --git a/doc/README.it.md b/doc/README.it.md
index bca6a23d2..55ae12380 100644
--- a/doc/README.it.md
+++ b/doc/README.it.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>ATTENZIONE: QUEST'APP È IN BETA, QUINDI POTRESTI IMBATTERTI IN <i>BUG</i>. SE COSÌ FOSSE, APRI UN'<i>ISSUE</i> SUL NOSTRO REPOSITORIO GITHUB COMPILANDO IL <i>TEMPLATE ISSUE</i>.</b>
-
-<b>METTERE NEWPIPE, O QUALSIASI SUA <i>FORK</i>, NEL GOOGLE PLAY STORE, VIOLA I LORO TERMINI E CONDIZIONI.</b>
+> [!warning]
+> <b>QUEST'APP È IN BETA, QUINDI POTRESTI IMBATTERTI IN <i>BUG</i>. SE COSÌ FOSSE, APRI UN'<i>ISSUE</i> SUL NOSTRO REPOSITORIO GITHUB COMPILANDO IL <i>TEMPLATE ISSUE</i>.</b>
+>
+> <b>METTERE NEWPIPE, O QUALSIASI SUA <i>FORK</i>, NEL GOOGLE PLAY STORE, VIOLA I LORO TERMINI E CONDIZIONI.</b>
 
 ## Screenshot
 
diff --git a/doc/README.ja.md b/doc/README.ja.md
index e4eb74694..4d0f348f6 100644
--- a/doc/README.ja.md
+++ b/doc/README.ja.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>
-
-<b>NewPipe 及びいずれのフォークを Google Play ストアに公開すると、Google の取引条件の違反になります。</b>
+> [!warning]
+> <b>これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>
+>
+> <b>NewPipe 及びいずれのフォークを Google Play ストアに公開すると、Google の取引条件の違反になります。</b>
 
 <span id="screenshots"></span>
 ## スクリーンショット
diff --git a/doc/README.ko.md b/doc/README.ko.md
index 0ba15ab43..dd9eb7705 100644
--- a/doc/README.ko.md
+++ b/doc/README.ko.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GitHub 저장소에서 Issue를 열람하여 주십시오.</b>
-
-<b>NewPipe 또는 NewPipe 포크를 구글 플레이스토어에 올리는 것은 그들의 이용약관을 위반합니다.</b>
+> [!warning]
+> <b>이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GitHub 저장소에서 Issue를 열람하여 주십시오.</b>
+>
+> <b>NewPipe 또는 NewPipe 포크를 구글 플레이스토어에 올리는 것은 그들의 이용약관을 위반합니다.</b>
 
 ## 스크린샷
 
diff --git a/doc/README.pa.md b/doc/README.pa.md
index d40e1b84f..321e6b7d0 100644
--- a/doc/README.pa.md
+++ b/doc/README.pa.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>ਚੇਤਾਵਨੀ: ਇਹ ਐਪ ਬੀਟਾ ਵਿੱਚ ਹੈ, ਇਸ ਲਈ ਤੁਸੀਂ ਬੱਗ ਦਾ ਸਾਹਮਣਾ ਕਰ ਸਕਦੇ ਹੋ। ਜੇਕਰ ਤੁਸੀਂ ਅਜਿਹਾ ਕਰਦੇ ਹੋ, ਤਾਂ ਟੈਮਪਲੇਟ ਨੂੰ ਭਰ ਕੇ ਸਾਡੀ ਗਿਟਹੱਬ ਰਿਪੋਜ਼ਟਰੀ ਵਿੱਚ ਇੱਕ ਮੁੱਦਾ ਖੋਲ੍ਹੋ</b>
-
-<b>ਗੂਗਲ ਪਲੇ ਸਟੋਰ ਵਿੱਚ ਨਿਊਪਾਈਪ ਜਾਂ ਇਸ ਦਾ ਕੋਈ ਵੀ ਫੋਰਕ ਲਗਾਉਣਾ ਉਹਨਾਂ ਦੇ ਨਿਯਮਾਂ ਅਤੇ ਸ਼ਰਤਾਂ ਦੀ ਉਲੰਘਣਾ ਕਰਦਾ ਹੈ।</b>
+> [!warning]
+> <b>ਇਹ ਐਪ ਬੀਟਾ ਵਿੱਚ ਹੈ, ਇਸ ਲਈ ਤੁਸੀਂ ਬੱਗ ਦਾ ਸਾਹਮਣਾ ਕਰ ਸਕਦੇ ਹੋ। ਜੇਕਰ ਤੁਸੀਂ ਅਜਿਹਾ ਕਰਦੇ ਹੋ, ਤਾਂ ਟੈਮਪਲੇਟ ਨੂੰ ਭਰ ਕੇ ਸਾਡੀ ਗਿਟਹੱਬ ਰਿਪੋਜ਼ਟਰੀ ਵਿੱਚ ਇੱਕ ਮੁੱਦਾ ਖੋਲ੍ਹੋ</b>
+>
+> <b>ਗੂਗਲ ਪਲੇ ਸਟੋਰ ਵਿੱਚ ਨਿਊਪਾਈਪ ਜਾਂ ਇਸ ਦਾ ਕੋਈ ਵੀ ਫੋਰਕ ਲਗਾਉਣਾ ਉਹਨਾਂ ਦੇ ਨਿਯਮਾਂ ਅਤੇ ਸ਼ਰਤਾਂ ਦੀ ਉਲੰਘਣਾ ਕਰਦਾ ਹੈ।</b>
 
 ## ਸਕਰੀਨਸ਼ਾਟ
 
diff --git a/doc/README.pl.md b/doc/README.pl.md
index 6412e10f5..64f75eaea 100644
--- a/doc/README.pl.md
+++ b/doc/README.pl.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>UWAGA: TO JEST WERSJA BETA, WIĘC MOŻESZ NAPOTKAĆ BŁĘDY. JEŚLI TAK SIĘ STANIE, OTWÓRZ NOWY "ISSUE" NA GITHUBIE.</b>
-
-<b>PUBLIKOWANIE NEWPIPE LUB JAKIEGOKOLWIEK JEGO FORKU W SKLEPIE PLAY NARUSZA ICH WARUNKI UŻYTKOWANIA.</b>
+> [!warning]
+> <b>TO JEST WERSJA BETA, WIĘC MOŻESZ NAPOTKAĆ BŁĘDY. JEŚLI TAK SIĘ STANIE, OTWÓRZ NOWY "ISSUE" NA GITHUBIE.</b>
+>
+> <b>PUBLIKOWANIE NEWPIPE LUB JAKIEGOKOLWIEK JEGO FORKU W SKLEPIE PLAY NARUSZA ICH WARUNKI UŻYTKOWANIA.</b>
 
 ## Screenshoty
 
diff --git a/doc/README.pt_BR.md b/doc/README.pt_BR.md
index a439adc68..01fa718ad 100644
--- a/doc/README.pt_BR.md
+++ b/doc/README.pt_BR.md
@@ -19,9 +19,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>
-
-<b>COLOCAR NEWPIPE OU QUALQUER FORK DELE NA GOOGLE PLAY STORE VIOLA SEUS TERMOS E CONDIÇÕES.</b>
+> [!warning]
+> <b>ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>
+>
+> <b>COLOCAR NEWPIPE OU QUALQUER FORK DELE NA GOOGLE PLAY STORE VIOLA SEUS TERMOS E CONDIÇÕES.</b>
 
 ## Screenshots
 
diff --git a/doc/README.ro.md b/doc/README.ro.md
index 77a6798bf..40e61e015 100644
--- a/doc/README.ro.md
+++ b/doc/README.ro.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>
-
-<b>PUNERA NEWPIPE SAU ORICĂRUI FORK AL ACESTUIA ÎN MAGAZINUL GOOGLE PLAY LE ÎNCALCĂ TERMENII ŞI CONDIŢIILE.</b>
+> [!warning]
+> <b>ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>
+>
+> <b>PUNERA NEWPIPE SAU ORICĂRUI FORK AL ACESTUIA ÎN MAGAZINUL GOOGLE PLAY LE ÎNCALCĂ TERMENII ŞI CONDIŢIILE.</b>
 
 ## Capturi de ecran
 
diff --git a/doc/README.ru.md b/doc/README.ru.md
index 1590b2399..35058c981 100644
--- a/doc/README.ru.md
+++ b/doc/README.ru.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>ВНИМАНИЕ: ЭТО ПРИЛОЖЕНИЕ НАХОДИТСЯ В СТАДИИ БЕТА, ПОЭТОМУ ВЫ МОЖЕТЕ ОБНАРУЖИТЬ ОШИБКИ. ЕСЛИ ЭТО СЛУЧИТСЯ, ОТКРОЙТЕ ВОПРОС В НАШЕМ РЕПОЗИТОРИИ GITHUB, ЗАПОЛНЯ ЕГО ПО ШАБЛОНУ.</b>
-
-<b>РАЗМЕЩЕНИЕ NEWPIPE ИЛИ ЕГО ОТВЕТВЛЕНИЙ В МАГАЗИНЕ GOOGLE PLAY НАРУШАЕТ ИХ УСЛОВИЯ И ПОЛОЖЕНИЯ.</b>
+> [!warning]
+> <b>ЭТО ПРИЛОЖЕНИЕ НАХОДИТСЯ В СТАДИИ БЕТА, ПОЭТОМУ ВЫ МОЖЕТЕ ОБНАРУЖИТЬ ОШИБКИ. ЕСЛИ ЭТО СЛУЧИТСЯ, ОТКРОЙТЕ ВОПРОС В НАШЕМ РЕПОЗИТОРИИ GITHUB, ЗАПОЛНЯ ЕГО ПО ШАБЛОНУ.</b>
+>
+> <b>РАЗМЕЩЕНИЕ NEWPIPE ИЛИ ЕГО ОТВЕТВЛЕНИЙ В МАГАЗИНЕ GOOGLE PLAY НАРУШАЕТ ИХ УСЛОВИЯ И ПОЛОЖЕНИЯ.</b>
 
 ## Скриншоты
 
diff --git a/doc/README.ryu.md b/doc/README.ryu.md
index fe30a9ba9..22ac6343d 100644
--- a/doc/README.ryu.md
+++ b/doc/README.ryu.md
@@ -18,9 +18,10 @@
 
 *ふかぬぎんぐっしゆむん: [English](../README.md), [Español](README.es.md), [हिन्दी](README.hi.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>ちゅうい:くれーベータばんぬたみ、バグぬはっせいすがのうゆいがあいびーん。むしバグぬはっせいさーあい、GitHubぬリポジトリっしIssueふぃらちくぃみそーれー。</b>
-
-<b>NewPipeうゆびいじりぬフォークGoogle Playストアんかいかんかいしーねー、Googleぬとぅいふぃちじょうきんぬいはんになります。</b>
+> [!warning]
+> <b>くれーベータばんぬたみ、バグぬはっせいすがのうゆいがあいびーん。むしバグぬはっせいさーあい、GitHubぬリポジトリっしIssueふぃらちくぃみそーれー。</b>
+>
+> <b>NewPipeうゆびいじりぬフォークGoogle Playストアんかいかんかいしーねー、Googleぬとぅいふぃちじょうきんぬいはんになります。</b>
 
 <span id="screenshots"></span>
 ## スクリーンショット
diff --git a/doc/README.so.md b/doc/README.so.md
index 56a696db3..1d3351256 100644
--- a/doc/README.so.md
+++ b/doc/README.so.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>
-
-<b>NEWPIPE AMA KUWA KU SALAYSAN IN PLAYSTORE-KA LA GALIYO WAXAY KA HOR IMANAYSAA SHARCIGA IYO SHURUUDAHA AY LEEYIHIIN.</b>
+> [!warning]
+> <b>MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>
+>
+> <b>NEWPIPE AMA KUWA KU SALAYSAN IN PLAYSTORE-KA LA GALIYO WAXAY KA HOR IMANAYSAA SHARCIGA IYO SHURUUDAHA AY LEEYIHIIN.</b>
 
 ## Sawir-shaashadeed
 
diff --git a/doc/README.sr.md b/doc/README.sr.md
index 775585f1a..7f0ee65b7 100644
--- a/doc/README.sr.md
+++ b/doc/README.sr.md
@@ -21,9 +21,10 @@
 
 *Прочитајте овај документ на другим језицима: [Deutsch](README.de.md), [English](README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [Српски](README.sr.md)*
 
-<b>УПОЗОРЕЊЕ: ОВА АПЛИКАЦИЈА ЈЕ У БЕТА ВЕРЗИЈИ, ТАКО ДА МОЖЕТЕ НАИЋИ НА ГРЕШКЕ. АКО НАИЂЕТЕ, НАПИШИТЕ ИЗВЕШТАЈ У НАШЕМ GITHUB РЕПОЗИТОРИЈУМУ ПОПУЊАВАЊЕМ ШАБЛОНА ИЗВЕШТАЈА.</b>
-
-<b>ПОСТАВЉАЊЕ АПЛИКАЦИЈЕ NEWPIPE ИЛИ БИЛО КОГ ЊЕНОГ ФОРКА, У GOOGLE PLAY ПРОДАВНИЦУ КРШИ ЊИХОВЕ УСЛОВЕ И ОДРЕДБЕ.</b>
+> [!warning]
+> <b>ОВА АПЛИКАЦИЈА ЈЕ У БЕТА ВЕРЗИЈИ, ТАКО ДА МОЖЕТЕ НАИЋИ НА ГРЕШКЕ. АКО НАИЂЕТЕ, НАПИШИТЕ ИЗВЕШТАЈ У НАШЕМ GITHUB РЕПОЗИТОРИЈУМУ ПОПУЊАВАЊЕМ ШАБЛОНА ИЗВЕШТАЈА.</b>
+>
+> <b>ПОСТАВЉАЊЕ АПЛИКАЦИЈЕ NEWPIPE ИЛИ БИЛО КОГ ЊЕНОГ ФОРКА, У GOOGLE PLAY ПРОДАВНИЦУ КРШИ ЊИХОВЕ УСЛОВЕ И ОДРЕДБЕ.</b>
 
 ## Снимци екрана
 
diff --git a/doc/README.tr.md b/doc/README.tr.md
index a9c91bfbd..d8accd5a7 100644
--- a/doc/README.tr.md
+++ b/doc/README.tr.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>UYARI: BU SÜRÜM BETA SÜRÜMÜDÜR, BU NEDENLE HATALARLA KARŞILAŞABİLİRSİNİZ. HATA BULURSANIZ BU GITHUB DEPOSUNDA BUNU BİLDİRİN.</b>
-
-<b>GOOGLE PLAY STORE'A NEWPIPE VEYA BAŞKA BİR KOPYASINI KOYMAK, PLAY STORE ŞARTLARINI VE KOŞULLARINI İHLAL EDER.</b>
+> [!warning]
+> <b>BU SÜRÜM BETA SÜRÜMÜDÜR, BU NEDENLE HATALARLA KARŞILAŞABİLİRSİNİZ. HATA BULURSANIZ BU GITHUB DEPOSUNDA BUNU BİLDİRİN.</b>
+>
+> <b>GOOGLE PLAY STORE'A NEWPIPE VEYA BAŞKA BİR KOPYASINI KOYMAK, PLAY STORE ŞARTLARINI VE KOŞULLARINI İHLAL EDER.</b>
 
 ## Ekran görüntüleri
 
diff --git a/doc/README.zh_TW.md b/doc/README.zh_TW.md
index a21f2ca4c..445618b1c 100644
--- a/doc/README.zh_TW.md
+++ b/doc/README.zh_TW.md
@@ -18,9 +18,10 @@
 
 *Read this document in other languages: [Deutsch](README.de.md), [English](../README.md), [Español](README.es.md), [Français](README.fr.md), [हिन्दी](README.hi.md), [Italiano](README.it.md), [한국어](README.ko.md), [Português Brasil](README.pt_BR.md), [Polski](README.pl.md), [ਪੰਜਾਬੀ ](README.pa.md), [日本語](README.ja.md), [Română](README.ro.md), [Soomaali](README.so.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md), [অসমীয়া](README.asm.md), [うちなーぐち](README.ryu.md), [Српски](README.sr.md)*
 
-<b>警告:這是測試版本,可能會發生錯誤。如果遇到錯誤,請在我們的 GITHUB REPO 開 ISSUE 回報。</b>
-
-<b>將 NEWPIPE 或其任何分支上傳至 GOOGLE PLAY 商店違反了他們的使用者合約。</b>
+> [!warning]
+> <b>這是測試版本,可能會發生錯誤。如果遇到錯誤,請在我們的 GITHUB REPO 開 ISSUE 回報。</b>
+>
+> <b>將 NEWPIPE 或其任何分支上傳至 GOOGLE PLAY 商店違反了他們的使用者合約。</b>
 
 <span id="screenshots"></span>
 ## 截圖

From 3a014d8d46a15935b403fe3fe5e4f74fb9976613 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 27 Mar 2024 16:05:17 +0100
Subject: [PATCH 106/141] Fix not considering nullability when comparing
 getAudioTrackType

---
 app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
index 7cb8f1f37..f1904565d 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java
@@ -874,6 +874,7 @@ public final class ListHelper {
 
         return Comparator.comparing(AudioStream::getAudioLocale, Comparator.nullsLast(
                         Comparator.comparing(locale -> locale.getDisplayName(appLoc))))
-                .thenComparing(AudioStream::getAudioTrackType);
+                .thenComparing(AudioStream::getAudioTrackType, Comparator.nullsLast(
+                        Comparator.naturalOrder()));
     }
 }

From bed351668738cbe57d528f2be77383771789abf9 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 27 Mar 2024 17:30:23 +0100
Subject: [PATCH 107/141] Fix non-desugared method being used

Search for "Not supported at all minSDK levels" here: https://developer.android.com/studio/write/java8-support-table
---
 .../java/us/shandian/giga/service/DownloadManagerService.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
index 42ff3ca8c..45211211f 100755
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -411,7 +411,7 @@ public class DownloadManagerService extends Service {
         mission.threadCount = threads;
         mission.source = source;
         mission.nearLength = nearLength;
-        mission.recoveryInfo = recovery.toArray(MissionRecoveryInfo[]::new);
+        mission.recoveryInfo = recovery.toArray(new MissionRecoveryInfo[0]);
 
         if (ps != null)
             ps.setTemporalDir(DownloadManager.pickAvailableTemporalDir(this));

From d8a5d5545ddae5f19566a30e949db551b24da732 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 10:56:50 +0100
Subject: [PATCH 108/141] Fix choosing audio format to mux with video-only
 download

---
 .../newpipe/util/SecondaryStreamHelper.java   | 54 +++++++------------
 1 file changed, 19 insertions(+), 35 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
index 75d9a3892..69dc697fe 100644
--- a/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/SecondaryStreamHelper.java
@@ -11,7 +11,6 @@ import org.schabi.newpipe.extractor.stream.Stream;
 import org.schabi.newpipe.extractor.stream.VideoStream;
 import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
 
-import java.util.Comparator;
 import java.util.List;
 
 public class SecondaryStreamHelper<T extends Stream> {
@@ -43,42 +42,27 @@ public class SecondaryStreamHelper<T extends Stream> {
                                                 @NonNull final List<AudioStream> audioStreams,
                                                 @NonNull final VideoStream videoStream) {
         final MediaFormat mediaFormat = videoStream.getFormat();
-        if (mediaFormat == null) {
+
+        if (mediaFormat == MediaFormat.WEBM) {
+            return audioStreams
+                    .stream()
+                    .filter(audioStream -> audioStream.getFormat() == MediaFormat.WEBMA
+                            || audioStream.getFormat() == MediaFormat.WEBMA_OPUS)
+                    .max(ListHelper.getAudioFormatComparator(MediaFormat.WEBMA,
+                            ListHelper.isLimitingDataUsage(context)))
+                    .orElse(null);
+
+        } else if (mediaFormat == MediaFormat.MPEG_4) {
+            return audioStreams
+                    .stream()
+                    .filter(audioStream -> audioStream.getFormat() == MediaFormat.M4A)
+                    .max(ListHelper.getAudioFormatComparator(MediaFormat.M4A,
+                            ListHelper.isLimitingDataUsage(context)))
+                    .orElse(null);
+
+        } else {
             return null;
         }
-
-        switch (mediaFormat) {
-            case WEBM:
-            case MPEG_4: // Is MPEG-4 DASH?
-                break;
-            default:
-                return null;
-        }
-
-        final boolean m4v = mediaFormat == MediaFormat.MPEG_4;
-        final boolean isLimitingDataUsage = ListHelper.isLimitingDataUsage(context);
-
-        Comparator<AudioStream> comparator = ListHelper.getAudioFormatComparator(
-                m4v ? MediaFormat.M4A : MediaFormat.WEBMA, isLimitingDataUsage);
-        int preferredAudioStreamIndex = ListHelper.getAudioIndexByHighestRank(
-                audioStreams, comparator);
-
-        if (preferredAudioStreamIndex == -1) {
-            if (m4v) {
-                return null;
-            }
-
-            comparator = ListHelper.getAudioFormatComparator(
-                    MediaFormat.WEBMA_OPUS, isLimitingDataUsage);
-            preferredAudioStreamIndex = ListHelper.getAudioIndexByHighestRank(
-                    audioStreams, comparator);
-
-            if (preferredAudioStreamIndex == -1) {
-                return null;
-            }
-        }
-
-        return audioStreams.get(preferredAudioStreamIndex);
     }
 
     public T getStream() {

From d3afde8789df76456128207a5f1186727f50237d Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 11:21:29 +0100
Subject: [PATCH 109/141] Remove unused DownloadDialog.onDismissListener

---
 .../newpipe/download/DownloadDialog.java      | 20 -------------------
 1 file changed, 20 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index fa312e55e..4680e71a7 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -7,8 +7,6 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
@@ -113,9 +111,6 @@ public class DownloadDialog extends DialogFragment
     @State
     int selectedSubtitleIndex = 0; // default to the first item
 
-    @Nullable
-    private OnDismissListener onDismissListener = null;
-
     private StoredDirectoryHelper mainStorageAudio = null;
     private StoredDirectoryHelper mainStorageVideo = null;
     private DownloadManager downloadManager = null;
@@ -195,13 +190,6 @@ public class DownloadDialog extends DialogFragment
         this.selectedVideoIndex = ListHelper.getDefaultResolutionIndex(context, videoStreams);
     }
 
-    /**
-     * @param onDismissListener the listener to call in {@link #onDismiss(DialogInterface)}
-     */
-    public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) {
-        this.onDismissListener = onDismissListener;
-    }
-
 
     /*//////////////////////////////////////////////////////////////////////////
     // Android lifecycle
@@ -364,14 +352,6 @@ public class DownloadDialog extends DialogFragment
         });
     }
 
-    @Override
-    public void onDismiss(@NonNull final DialogInterface dialog) {
-        super.onDismiss(dialog);
-        if (onDismissListener != null) {
-            onDismissListener.onDismiss(dialog);
-        }
-    }
-
     @Override
     public void onDestroy() {
         super.onDestroy();

From 5d9adce40dce1e10507131c2eb39c348ba2a6c50 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 11:35:21 +0100
Subject: [PATCH 110/141] Fix NPE, since dismissing a dialog still calls
 onViewCreated()

---
 .../java/org/schabi/newpipe/download/DownloadDialog.java   | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index 4680e71a7..bbdb46292 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -115,7 +115,7 @@ public class DownloadDialog extends DialogFragment
     private StoredDirectoryHelper mainStorageVideo = null;
     private DownloadManager downloadManager = null;
     private ActionMenuItemView okButton = null;
-    private Context context;
+    private Context context = null;
     private boolean askForSavePath;
 
     private AudioTrackAdapter audioTrackAdapter;
@@ -209,6 +209,8 @@ public class DownloadDialog extends DialogFragment
             return;
         }
 
+        // context will remain null if dismiss() was called above, allowing to check whether the
+        // dialog is being dismissed in onViewCreated()
         context = getContext();
 
         setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
@@ -293,6 +295,9 @@ public class DownloadDialog extends DialogFragment
                               @Nullable final Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         dialogBinding = DownloadDialogBinding.bind(view);
+        if (context == null) {
+            return; // the dialog is being dismissed, see the call to dismiss() in onCreate()
+        }
 
         dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
                 currentInfo.getName()));

From 29e4135aaa168db96e67a9168acbb01da92c65d6 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 12:03:10 +0100
Subject: [PATCH 111/141] Try to fix PR labeler

Reference: https://github.com/actions/labeler?tab=readme-ov-file#permissions
---
 .github/workflows/pr-labeler.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index e0dba3111..a18daca3a 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -1,5 +1,5 @@
 name: "PR size labeler"
-on: [pull_request]
+on: [pull_request_target]
 permissions:
   contents: read
   pull-requests: write

From ddd6c8cbf1e6a019f4df82863e3b446d5fb708fb Mon Sep 17 00:00:00 2001
From: ge78fug <ge78fug@mytum.de>
Date: Mon, 27 Mar 2023 16:57:30 +0200
Subject: [PATCH 112/141] Changed the landscape layout of list card item

Make layout-land/list_stream_card_item a symlink to layout/list_stream_item
---
 app/src/main/res/layout-land/list_stream_card_item.xml | 1 +
 1 file changed, 1 insertion(+)
 create mode 120000 app/src/main/res/layout-land/list_stream_card_item.xml

diff --git a/app/src/main/res/layout-land/list_stream_card_item.xml b/app/src/main/res/layout-land/list_stream_card_item.xml
new file mode 120000
index 000000000..70228ee1d
--- /dev/null
+++ b/app/src/main/res/layout-land/list_stream_card_item.xml
@@ -0,0 +1 @@
+../layout/list_stream_item.xml
\ No newline at end of file

From cefdefdfd2d82dd461d8736fe497a709457288b0 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 18:51:36 +0100
Subject: [PATCH 113/141] 11111th commit


From b85f7a6747eb61ea4b7b7893688ea65f7103de01 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Thu, 28 Mar 2024 22:46:19 +0100
Subject: [PATCH 114/141] Some more slight improvements

---
 .../newpipe/fragments/detail/DescriptionFragment.java |  2 +-
 .../fragments/list/channel/ChannelAboutFragment.java  | 11 +++--------
 .../fragments/list/channel/ChannelFragment.java       |  2 +-
 3 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
index ba233559c..7fc66b42f 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
@@ -26,11 +26,11 @@ public class DescriptionFragment extends BaseDescriptionFragment {
     @State
     StreamInfo streamInfo;
 
-
     public DescriptionFragment(final StreamInfo streamInfo) {
         this.streamInfo = streamInfo;
     }
 
+
     @Nullable
     @Override
     protected Description getDescription() {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
index 30c3aac5a..674a13c5b 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
@@ -26,10 +26,8 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
     @State
     protected ChannelInfo channelInfo;
 
-    public static ChannelAboutFragment getInstance(final @NonNull ChannelInfo channelInfo) {
-        final ChannelAboutFragment fragment = new ChannelAboutFragment();
-        fragment.channelInfo = channelInfo;
-        return fragment;
+    ChannelAboutFragment(@NonNull final ChannelInfo channelInfo) {
+        this.channelInfo = channelInfo;
     }
 
 
@@ -42,10 +40,7 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
     @Nullable
     @Override
     protected Description getDescription() {
-        return new Description(
-                channelInfo.getDescription(),
-                Description.PLAIN_TEXT
-        );
+        return new Description(channelInfo.getDescription(), Description.PLAIN_TEXT);
     }
 
     @NonNull
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
index b16f40a4a..7e83d9958 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
@@ -474,7 +474,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
             if (ChannelTabHelper.showChannelTab(
                     context, preferences, R.string.show_channel_tabs_about)) {
                 tabAdapter.addFragment(
-                        ChannelAboutFragment.getInstance(currentInfo),
+                        new ChannelAboutFragment(currentInfo),
                         context.getString(R.string.channel_tab_about));
             }
         }

From 1d3a69a29f7f8237a1c6df55a7c39a66a6e435e1 Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Thu, 25 Jan 2024 15:50:47 +0100
Subject: [PATCH 115/141] Move text from manual_update_title to
 check_for_updates

---
 app/src/main/res/values-ar-rLY/strings.xml | 2 +-
 app/src/main/res/values-ar/strings.xml     | 2 +-
 app/src/main/res/values-az/strings.xml     | 2 +-
 app/src/main/res/values-be/strings.xml     | 2 +-
 app/src/main/res/values-bg/strings.xml     | 2 +-
 app/src/main/res/values-bn-rBD/strings.xml | 2 +-
 app/src/main/res/values-bn/strings.xml     | 2 +-
 app/src/main/res/values-ca/strings.xml     | 2 +-
 app/src/main/res/values-ckb/strings.xml    | 2 +-
 app/src/main/res/values-cs/strings.xml     | 2 +-
 app/src/main/res/values-da/strings.xml     | 2 +-
 app/src/main/res/values-de/strings.xml     | 2 +-
 app/src/main/res/values-el/strings.xml     | 2 +-
 app/src/main/res/values-es/strings.xml     | 2 +-
 app/src/main/res/values-et/strings.xml     | 2 +-
 app/src/main/res/values-eu/strings.xml     | 2 +-
 app/src/main/res/values-fa/strings.xml     | 2 +-
 app/src/main/res/values-fi/strings.xml     | 2 +-
 app/src/main/res/values-fr/strings.xml     | 2 +-
 app/src/main/res/values-gl/strings.xml     | 2 +-
 app/src/main/res/values-he/strings.xml     | 2 +-
 app/src/main/res/values-hi/strings.xml     | 2 +-
 app/src/main/res/values-hr/strings.xml     | 2 +-
 app/src/main/res/values-hu/strings.xml     | 2 +-
 app/src/main/res/values-hy/strings.xml     | 2 +-
 app/src/main/res/values-in/strings.xml     | 2 +-
 app/src/main/res/values-is/strings.xml     | 2 +-
 app/src/main/res/values-it/strings.xml     | 2 +-
 app/src/main/res/values-ja/strings.xml     | 2 +-
 app/src/main/res/values-ka/strings.xml     | 2 +-
 app/src/main/res/values-ko/strings.xml     | 2 +-
 app/src/main/res/values-lt/strings.xml     | 2 +-
 app/src/main/res/values-lv/strings.xml     | 2 +-
 app/src/main/res/values-nb-rNO/strings.xml | 2 +-
 app/src/main/res/values-nl/strings.xml     | 2 +-
 app/src/main/res/values-nqo/strings.xml    | 2 +-
 app/src/main/res/values-or/strings.xml     | 2 +-
 app/src/main/res/values-pa/strings.xml     | 2 +-
 app/src/main/res/values-pl/strings.xml     | 2 +-
 app/src/main/res/values-pt-rBR/strings.xml | 2 +-
 app/src/main/res/values-pt-rPT/strings.xml | 2 +-
 app/src/main/res/values-pt/strings.xml     | 2 +-
 app/src/main/res/values-ro/strings.xml     | 2 +-
 app/src/main/res/values-ru/strings.xml     | 2 +-
 app/src/main/res/values-ryu/strings.xml    | 2 +-
 app/src/main/res/values-sc/strings.xml     | 2 +-
 app/src/main/res/values-sk/strings.xml     | 2 +-
 app/src/main/res/values-sr/strings.xml     | 2 +-
 app/src/main/res/values-sv/strings.xml     | 2 +-
 app/src/main/res/values-tr/strings.xml     | 2 +-
 app/src/main/res/values-uk/strings.xml     | 2 +-
 app/src/main/res/values-vi/strings.xml     | 2 +-
 app/src/main/res/values-zh-rCN/strings.xml | 2 +-
 app/src/main/res/values-zh-rHK/strings.xml | 2 +-
 app/src/main/res/values-zh-rTW/strings.xml | 2 +-
 app/src/main/res/values/strings.xml        | 3 ++-
 56 files changed, 57 insertions(+), 56 deletions(-)

diff --git a/app/src/main/res/values-ar-rLY/strings.xml b/app/src/main/res/values-ar-rLY/strings.xml
index c57ead36a..077cf1106 100644
--- a/app/src/main/res/values-ar-rLY/strings.xml
+++ b/app/src/main/res/values-ar-rLY/strings.xml
@@ -78,7 +78,7 @@
     <string name="short_billion">بليون</string>
     <string name="feed_load_error_account_info">تعذر تحميل موجز \'%s\'.</string>
     <string name="question_mark">؟</string>
-    <string name="manual_update_title">التحقق من وجود تحديثات</string>
+    <string name="check_for_updates">التحقق من وجود تحديثات</string>
     <string name="peertube_instance_url_title">مثيلات خوادم پيرتيوب</string>
     <string name="more_than_100_videos">+100 فيديو</string>
     <string name="short_thousand">ألف</string>
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index d69564493..eefe24390 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -700,7 +700,7 @@
     <string name="enqueue_next_stream">وضع التالي على قائمة الانتظار</string>
     <string name="enqueued_next">تم وضع التالي على قائمة الانتظار</string>
     <string name="processing_may_take_a_moment">جاري المعالجة ... قد يستغرق لحظة</string>
-    <string name="manual_update_title">التحقق من وجود تحديثات</string>
+    <string name="check_for_updates">التحقق من وجود تحديثات</string>
     <string name="manual_update_description">التحقق يدويا من وجود إصدارات جديدة</string>
     <string name="checking_updates_toast">جاري التحقق من وجود تحديثات…</string>
     <string name="feed_new_items">عناصر تغذية جديدة</string>
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index c0ac7e6d5..66bfe75de 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -448,7 +448,7 @@
         <item quantity="one">%s video</item>
         <item quantity="other">%s video</item>
     </plurals>
-    <string name="manual_update_title">Yeniləmələri yoxla</string>
+    <string name="check_for_updates">Yeniləmələri yoxla</string>
     <string name="seekbar_preview_thumbnail_title">Axtarış çubuğunun miniatür önizləməsi</string>
     <string name="permission_denied">Əməliyyat sistem tərəfindən ləğv edildi</string>
     <string name="auto">Avto</string>
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index c41a9e6bb..5990738cf 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -670,7 +670,7 @@
     <string name="enable_streams_notifications_summary">Апавяшчаць аб новых стрымах з падпісак</string>
     <string name="streams_notifications_interval_title">Частата праверкі</string>
     <string name="streams_notifications_network_title">Патрабуецца падключэнне да сеткі</string>
-    <string name="manual_update_title">Праверце наяўнасць абнаўленняў</string>
+    <string name="check_for_updates">Праверце наяўнасць абнаўленняў</string>
     <string name="manual_update_description">Праверце новыя версіі ўручную</string>
     <string name="autoplay_summary">Аўтаматычны запуск прайгравання — %s</string>
     <string name="card">Картка</string>
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 78dcd0a93..bc235446c 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -548,7 +548,7 @@
     <string name="recaptcha_cookies_cleared">Бисквитките от reCAPTCHA бяха почистени</string>
     <string name="checking_updates_toast">Проверяване за актуализации…</string>
     <string name="enumeration_comma">,</string>
-    <string name="manual_update_title">Провери за актуализации</string>
+    <string name="check_for_updates">Провери за актуализации</string>
     <string name="percent">Процент</string>
     <string name="unknown_quality">Неизвестно качество</string>
     <string name="unknown_format">Неизвестен формат</string>
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 7ac77e353..3f0d65c82 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -401,7 +401,7 @@
     <string name="default_content_country_title">কনটেন্টের জন্য পূর্বনির্ধারিত দেশ</string>
     <string name="external_player_unsupported_link_type">বাইরের প্লেয়ারসমূহ এ ধরনের লিঙ্কসমূহ সমর্থন করে না</string>
     <string name="msg_calculating_hash">হ্যাশ হিসাব করা হচ্ছে</string>
-    <string name="manual_update_title">আপডেট চেক করো</string>
+    <string name="check_for_updates">আপডেট চেক করো</string>
     <plurals name="watching">
         <item quantity="one">%s জন দেখছে</item>
         <item quantity="other">%s জন দেখছে</item>
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index c0851f9d6..853b04b64 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -596,7 +596,7 @@
     <string name="detail_pinned_comment_view_description">পিনকৃত মন্তব্য</string>
     <string name="notifications">বিজ্ঞপ্তি</string>
     <string name="checking_updates_toast">হালনাগাদ দেখা হচ্ছে …</string>
-    <string name="manual_update_title">হালনাগাদ আছে কিনা দেখো</string>
+    <string name="check_for_updates">হালনাগাদ আছে কিনা দেখো</string>
     <string name="start_main_player_fullscreen_title">মূল প্লেয়ার ফুল স্ক্রীন এ শুরু করুন</string>
     <string name="feed_new_items">ধারার নতুন ভুক্তি</string>
     <string name="error_report_channel_name">ত্রুটি প্রতিবেদন এর বিজ্ঞপ্তি</string>
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 19a74923f..871af187b 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -640,7 +640,7 @@
     <string name="start_main_player_fullscreen_summary">Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa</string>
     <string name="error_report_channel_name">Notificació d\'informe d\'error</string>
     <string name="crash_the_player">Tancar abruptament el reproductor</string>
-    <string name="manual_update_title">Comprovar si hi ha actualitzacions</string>
+    <string name="check_for_updates">Comprovar si hi ha actualitzacions</string>
     <string name="manual_update_description">Comprovar manualment si hi ha noves versions</string>
     <plurals name="download_finished_notification">
         <item quantity="one">Baixada finalitzada</item>
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index 7c60b33c0..e6e375a4c 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -658,7 +658,7 @@
     <string name="error_report_notification_title">نیوپایپ تووشی کێشەیەک بوو ، کرتە بکە بۆ سکاڵاکردن</string>
     <string name="show_crash_the_player_title">پیشاندانی ”کڕاش کردنی لێدەرەکە“</string>
     <string name="create_error_notification">سازاندنی پەیامی کێشەیەک</string>
-    <string name="manual_update_title">پشکنین بۆ نوێکردنەوە</string>
+    <string name="check_for_updates">پشکنین بۆ نوێکردنەوە</string>
     <string name="error_report_channel_name">کێشە لە سکاڵا کردنی پەیام</string>
     <string name="error_report_channel_description">پەیامەکانی سکاڵاکردن لە کێشەکان</string>
     <string name="feed_new_items">بابەتە نوێیەکانی فیید</string>
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 02f6486c9..0a4751261 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -665,7 +665,7 @@
     <string name="start_main_player_fullscreen_title">Spustit hlavní přehrávač na celé obrazovce</string>
     <string name="processing_may_take_a_moment">Zpracovávám... může trvat moment</string>
     <string name="manual_update_description">Ručně zkontrolovat zda je k dispozici nová verze</string>
-    <string name="manual_update_title">Kontrola aktualizací</string>
+    <string name="check_for_updates">Kontrola aktualizací</string>
     <string name="error_report_notification_title">NewPipe narazil na problém, klikněte pro nahlášení</string>
     <string name="error_report_notification_toast">Došlo k chybě, více v oznámení</string>
     <string name="create_error_notification">Vytvořit oznámení o chybě</string>
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index f48ed7b3b..601bc3752 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -526,7 +526,7 @@
     <string name="checking_updates_toast">Tjekker efter opdateringer…</string>
     <string name="recovering">gendanner</string>
     <string name="feed_load_error_fast_unknown">\"Hurtig feed\"-tilstand oplyser ikke mere info om dette.</string>
-    <string name="manual_update_title">Tjek efter opdateringer</string>
+    <string name="check_for_updates">Tjek efter opdateringer</string>
     <string name="remove_watched_popup_title">Fjern sete videoer\?</string>
     <string name="disable_media_tunneling_summary">Deaktivér medietunneling, hvis du oplever en sort skærm eller hakken ved videoafspilning.</string>
     <string name="error_report_open_github_notice">Tjek venligst, om der allerede findes et problem, der diskuterer dit nedbrud. Når du opretter flere tickets, tager du tid fra os, som vi kunne bruge på at løse den faktiske fejl.</string>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index c7506c649..e94c8f757 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -655,7 +655,7 @@
     <string name="enqueued_next">Als Nächstes eingereiht</string>
     <string name="enqueue_next_stream">Als Nächstes in Wiedergabe einreihen</string>
     <string name="processing_may_take_a_moment">Verarbeite … Kann einen Moment dauern</string>
-    <string name="manual_update_title">Nach Aktualisierungen suchen</string>
+    <string name="check_for_updates">Nach Aktualisierungen suchen</string>
     <string name="checking_updates_toast">Suche nach Aktualisierungen …</string>
     <string name="manual_update_description">Manuelle Prüfung auf neue Versionen</string>
     <string name="feed_new_items">Neue Feed-Elemente</string>
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 18ab48318..cb5958dc5 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -654,7 +654,7 @@
     <string name="processing_may_take_a_moment">Επεξεργασία... Μπορεί να πάρει λίγο χρόνο</string>
     <string name="checking_updates_toast">Έλεγχος αναβάθμισης…</string>
     <string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string>
-    <string name="manual_update_title">Έλεγχος αναβάθμισης</string>
+    <string name="check_for_updates">Έλεγχος αναβάθμισης</string>
     <string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string>
     <string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευσης αναπαραγωγέα»</string>
     <string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index b17c4d169..3a8681e44 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -667,7 +667,7 @@
     <string name="enqueued_next">Añadido el siguiente vídeo a la cola</string>
     <string name="enqueue_next_stream">Añadir el siguiente vídeo a la cola</string>
     <string name="processing_may_take_a_moment">Procesando… Podría tomar un momento</string>
-    <string name="manual_update_title">Buscar actualizaciones</string>
+    <string name="check_for_updates">Buscar actualizaciones</string>
     <string name="manual_update_description">Buscar nuevas versiones manualmente</string>
     <string name="checking_updates_toast">Buscando actualizaciones…</string>
     <string name="feed_new_items">Nuevos elementos en el muro</string>
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 1234aec5e..5864d5519 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -653,7 +653,7 @@
     <string name="enqueue_next_stream">Lisa esitamiseks järgmisena</string>
     <string name="processing_may_take_a_moment">Töötlen andmeid… Võib kuluda mõni hetk</string>
     <string name="checking_updates_toast">Kontrollin uuendusi…</string>
-    <string name="manual_update_title">Kontrolli uuendusi</string>
+    <string name="check_for_updates">Kontrolli uuendusi</string>
     <string name="manual_update_description">Kontrolli uuendusi käsitsi</string>
     <string name="feed_new_items">Uued andmevoo kirjed</string>
     <string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest</string>
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 3c36a85ae..6f94a8f62 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -657,7 +657,7 @@
     <string name="error_report_channel_description">Jakinarazpenak erroreen berri emateko</string>
     <string name="error_report_notification_title">NewPipe-k errore bat aurkitu du, sakatu berri emateko</string>
     <string name="error_report_notification_toast">Errore bat gertatu da, ikusi jakinarazpena</string>
-    <string name="manual_update_title">Bilatu eguneraketak</string>
+    <string name="check_for_updates">Bilatu eguneraketak</string>
     <string name="manual_update_description">Bilatu bertsio berriak eskuz</string>
     <string name="feed_new_items">Elementu berriak jarioan</string>
     <string name="no_appropriate_file_manager_message_android_10">Ez da fitxategi kudeatzaile bat aurkitu ekintza honetarako.
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 01d26857e..2afeaf286 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -652,7 +652,7 @@
     <string name="enqueued_next">بعدی در صف گذاشته شد</string>
     <string name="enqueue_next_stream">در صف گذاشتن بعدی</string>
     <string name="processing_may_take_a_moment">در حال پردازش… ممکن است کمی طول بکشد</string>
-    <string name="manual_update_title">بررسی به‌روز رسانی‌ها</string>
+    <string name="check_for_updates">بررسی به‌روز رسانی‌ها</string>
     <string name="manual_update_description">بررسی دستی برای نگارش‌های جدید</string>
     <string name="checking_updates_toast">بررسی کردن به‌روز رسانی‌ها…</string>
     <string name="feed_new_items">موارد خوراک جدید</string>
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index c27115689..67350d7ba 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -652,7 +652,7 @@
         <item quantity="other">Poistettu %1$s latausta</item>
     </plurals>
     <string name="processing_may_take_a_moment">Käsitellään… Voi kestää hetken</string>
-    <string name="manual_update_title">Tarkista päivitykset</string>
+    <string name="check_for_updates">Tarkista päivitykset</string>
     <string name="manual_update_description">Tarkista manuaalisesti onko uusia versioita saatavilla</string>
     <string name="checking_updates_toast">Tarkistetaan päivityksiä…</string>
     <string name="error_report_channel_description">Ilmoitukset, joilla raportoidaan virheistä</string>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 51b572dce..9994abb4c 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -668,7 +668,7 @@
     <string name="processing_may_take_a_moment">Traitement en cours… Veuillez patienter</string>
     <string name="manual_update_description">Vérifier manuellement de nouvelles versions</string>
     <string name="checking_updates_toast">Vérification des mises à jour…</string>
-    <string name="manual_update_title">Vérifier les mises à jour</string>
+    <string name="check_for_updates">Vérifier les mises à jour</string>
     <string name="feed_new_items">Nouveaux éléments du flux</string>
     <string name="crash_the_player">Faire planter le lecteur</string>
     <string name="show_crash_the_player_title">Afficher « Faire planter le lecteur »</string>
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 93a43ddd2..efe1b7f5e 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -658,7 +658,7 @@
     <string name="error_report_notification_title">NewPipe atopou un erro, presione para reportar</string>
     <string name="detail_pinned_comment_view_description">Comentario fixado</string>
     <string name="enqueued_next">Enfileirado</string>
-    <string name="manual_update_title">Procurar actualizacións</string>
+    <string name="check_for_updates">Procurar actualizacións</string>
     <string name="manual_update_description">Procurar manualmente novas versións</string>
     <string name="checking_updates_toast">A procurar actualizacións…</string>
     <string name="downloads_storage_use_saf_summary_api_29">A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado</string>
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index 6cae41316..befde2d53 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -676,7 +676,7 @@
     <string name="enqueued_next">נוסף כהבא בתור</string>
     <string name="enqueue_next_stream">הוספה כהבא בתור</string>
     <string name="processing_may_take_a_moment">מתבצע עיבוד… נא להמתין רגע קט</string>
-    <string name="manual_update_title">איתור עדכונים</string>
+    <string name="check_for_updates">איתור עדכונים</string>
     <string name="checking_updates_toast">מתבצע איתור עדכונים…</string>
     <string name="manual_update_description">לנסות לאתר גרסאות חדשות ידנית</string>
     <string name="feed_new_items">פריטים חדשים בהזנה</string>
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index a425a9b04..756b1878a 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -579,7 +579,7 @@
     <string name="streams_notifications_network_title">नेटवर्क कनेक्शन आवश्यक</string>
     <string name="enable_streams_notifications_title">नई स्ट्रीम अधिसूचनाएं</string>
     <string name="any_network">कोई भी नेटवर्क</string>
-    <string name="manual_update_title">अपडेट के लिए जाँच करें</string>
+    <string name="check_for_updates">अपडेट के लिए जाँच करें</string>
     <string name="low_quality_smaller">निम्न गुणवत्ता (छोटा)</string>
     <string name="seekbar_preview_thumbnail_title">सीकबार थंमनेल पूर्वावलोकन</string>
     <string name="high_quality_larger">उच्च गुणवत्ता (बड़ा)</string>
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 8f077fdea..f6c2120f7 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -665,7 +665,7 @@
     <string name="manual_update_description">Ručno traži nove verzije</string>
     <string name="checking_updates_toast">Traženje novih verzija …</string>
     <string name="local_search_suggestions">Prijedlozi lokalne pretrage</string>
-    <string name="manual_update_title">Traži nove verzije</string>
+    <string name="check_for_updates">Traži nove verzije</string>
     <string name="start_main_player_fullscreen_summary">Nemoj pokretati videa u mini playeru, već izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini playeru i dalje možeš pristupiti napuštanjem cjeloekranskog prikaza</string>
     <string name="feed_new_items">Nove stavke feeda</string>
     <string name="error_report_channel_name">Obavijest o prijavi greške</string>
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 13a488f07..5ca829ed8 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -661,7 +661,7 @@
     <string name="show_crash_the_player_title">A „lejátszó összeomlasztása” lehetőség megjelenítése</string>
     <string name="show_crash_the_player_summary">Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor</string>
     <string name="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string>
-    <string name="manual_update_title">Frissítések keresése</string>
+    <string name="check_for_updates">Frissítések keresése</string>
     <string name="dont_show">Ne jelenítse meg</string>
     <string name="remove_watched">Megnézettek eltávolítása</string>
     <string name="remove_watched_popup_title">Eltávolítja a megnézett videókat\?</string>
diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml
index 5becf32fb..dd307a2fa 100644
--- a/app/src/main/res/values-hy/strings.xml
+++ b/app/src/main/res/values-hy/strings.xml
@@ -151,7 +151,7 @@
     <string name="resize_zoom">Մեծացնել</string>
     <string name="caption_auto_generated">Գեներացված</string>
     <string name="import_file_title">Ներմուծել ֆայլ</string>
-    <string name="manual_update_title">Ստուգել թարմացումները</string>
+    <string name="check_for_updates">Ստուգել թարմացումները</string>
     <string name="auto">Ինքնին</string>
     <string name="high_quality_larger">Բարձր որակ (մեծ)</string>
     <string name="low_quality_smaller">Ցածր որակ (փոքր)</string>
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 1ca35cfb8..d715f2232 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -640,7 +640,7 @@
     <string name="show_image_indicators_summary">Tampilkan Ribon bewarna Picasso di atas gambar yang mengindikasikan asalnya: merah untuk jaringan, biru untuk disk dan hijau untuk memori</string>
     <string name="start_main_player_fullscreen_summary">Jangan memulai memutar video di mini player, tapi nyalakan langsung di mode layar penuh, jika rotasi otomatis terkunci. Anda tetap dapat mengakses mini player dengan keluar dari layar penuh</string>
     <string name="processing_may_take_a_moment">Memproses… Mungkin butuh waktu sebentar</string>
-    <string name="manual_update_title">Periksa Pembaruan</string>
+    <string name="check_for_updates">Periksa Pembaruan</string>
     <string name="manual_update_description">Periksa manual untuk versi baru</string>
     <string name="checking_updates_toast">Memeriksa pembaruan…</string>
     <string name="feed_new_items">Item feed baru</string>
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index c35d7a685..ed5ebe99b 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -438,7 +438,7 @@
     <string name="streams_notifications_network_title">Tegund tengingar</string>
     <string name="any_network">Allar</string>
     <string name="updates_setting_description">Sýna tilkynningu þegar ný útgáfa er fáanleg</string>
-    <string name="manual_update_title">Leita að uppfærslum</string>
+    <string name="check_for_updates">Leita að uppfærslum</string>
     <string name="manual_update_description">Leita handvirkt fyrir uppfærslum</string>
     <string name="minimize_on_exit_title">Fela þegar skipt er um forrit</string>
     <string name="minimize_on_exit_background_description">Nota bakgrunnsspilara</string>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 3d8162880..5ddb6c672 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -664,7 +664,7 @@
     <string name="enqueued_next">Aggiunto alla coda come prossimo</string>
     <string name="enqueue_next_stream">Accoda come prossimo</string>
     <string name="processing_may_take_a_moment">Elaborazione… Potrebbe volerci un attimo</string>
-    <string name="manual_update_title">Controlla aggiornamenti</string>
+    <string name="check_for_updates">Controlla aggiornamenti</string>
     <string name="manual_update_description">Verifica manualmente la presenza di nuove versioni</string>
     <string name="checking_updates_toast">Controllo aggiornamenti…</string>
     <string name="feed_new_items">Nuovi elementi feed</string>
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 03117abad..afcf34429 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -639,7 +639,7 @@
     <string name="processing_may_take_a_moment">処理中… 少し時間がかかるかもしれません</string>
     <string name="manual_update_description">新しいバージョンを手動で確認します</string>
     <string name="checking_updates_toast">アップデートを確認中…</string>
-    <string name="manual_update_title">アップデートを確認</string>
+    <string name="check_for_updates">アップデートを確認</string>
     <string name="enqueue_next_stream">次をキューに追加</string>
     <string name="enqueued_next">次をキューに追加しました</string>
     <string name="detail_heart_img_view_description">クリエイターの心をこめて</string>
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index cbeb27d9d..ecb2a8495 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -426,7 +426,7 @@
     <string name="any_network">ნებისმიერი ქსელი</string>
     <string name="updates_setting_title">განახლებები</string>
     <string name="updates_setting_description">მაჩვენე შეტყობინება აპის განახლების მოთხოვნით, როდესაც ხელმისაწვდომი იქნება ახალი ვერსია</string>
-    <string name="manual_update_title">Შეამოწმოთ განახლებები</string>
+    <string name="check_for_updates">Შეამოწმოთ განახლებები</string>
     <string name="manual_update_description">ხელით შეამოწმეთ ახალი ვერსიები</string>
     <string name="minimize_on_exit_title">მინიმიზაცია აპის გადამრთველზე</string>
     <string name="minimize_on_exit_summary">მოქმედება სხვა აპზე გადასვლისას მთავარი ვიდეო დამკვრელიდან — %s</string>
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index d03be0920..d17e7599d 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -557,7 +557,7 @@
     <string name="streams_notifications_interval_title">확인 빈도</string>
     <string name="streams_notifications_network_title">필요한 네트워크 연결</string>
     <string name="any_network">모든 네트워크</string>
-    <string name="manual_update_title">업데이트 확인</string>
+    <string name="check_for_updates">업데이트 확인</string>
     <string name="manual_update_description">새로운 버전을 수동으로 확인</string>
     <string name="autoplay_summary">자동으로 재생 시작 — %s</string>
     <string name="wifi_only">Wi-Fi에서만</string>
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 84699cf6d..c68e49bdd 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -664,7 +664,7 @@
     <string name="enqueued_next">Sekantis pridėtas į eilę</string>
     <string name="enqueue_next_stream">Įtraukti į eilę sekantį</string>
     <string name="processing_may_take_a_moment">Apdorojama… tai gali užtrukti</string>
-    <string name="manual_update_title">Tikrinti ar yra atnaujinimų</string>
+    <string name="check_for_updates">Tikrinti ar yra atnaujinimų</string>
     <string name="manual_update_description">Tikrinti ar yra atnaujinimų rankiniu būdu</string>
     <string name="checking_updates_toast">Tikrinti ar yra atnaujinimų…</string>
     <string name="feed_new_items">Nauji sklaidos kanalo elementai</string>
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index ab3f7b94c..58b9a9d76 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -632,7 +632,7 @@
     <string name="local_search_suggestions">Lokālie meklēšanas ieteikumi</string>
     <string name="show_image_indicators_title">Rādīt attēlu indikatorus</string>
     <string name="high_quality_larger">Augstas kvalitātes (lielāks)</string>
-    <string name="manual_update_title">Pārbaudīt atjauninājumus</string>
+    <string name="check_for_updates">Pārbaudīt atjauninājumus</string>
     <string name="manual_update_description">Manuāli pārbaudīt, vai ir atjauninājumi</string>
     <string name="seekbar_preview_thumbnail_title">Video atskaņošanas joslas sīktēla priekšskatījums</string>
     <string name="checking_updates_toast">Pārbauda, vai ir atjauninājumi…</string>
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 6f232ba06..416ebfd02 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -651,7 +651,7 @@
     <string name="start_main_player_fullscreen_title">Start hovedspiller i fullskjerm</string>
     <string name="enqueue_next_stream">Still i kø neste</string>
     <string name="enqueued_next">I kø neste</string>
-    <string name="manual_update_title">Se etter oppdateringer</string>
+    <string name="check_for_updates">Se etter oppdateringer</string>
     <string name="processing_may_take_a_moment">Behandler … Kan ta sin tid</string>
     <string name="crash_the_player">Krasj avspilleren</string>
     <string name="show_crash_the_player_title">Vis \"Krasj spilleren\"</string>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index edb822366..9b690f770 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -656,7 +656,7 @@
     <string name="show_crash_the_player_title">‘Speler crashen’ tonen</string>
     <string name="show_crash_the_player_summary">Toont een crash-optie bij gebruik van de speler</string>
     <string name="manual_update_description">Controleer handmatig op nieuwe versies</string>
-    <string name="manual_update_title">Controleren op updates</string>
+    <string name="check_for_updates">Controleren op updates</string>
     <string name="checking_updates_toast">Bezig met controleren op updates…</string>
     <string name="feed_new_items">Nieuwe feeditems</string>
     <string name="error_report_channel_name">Foutrapporten</string>
diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml
index 6956d6b78..94e7ae2f4 100644
--- a/app/src/main/res/values-nqo/strings.xml
+++ b/app/src/main/res/values-nqo/strings.xml
@@ -482,7 +482,7 @@
     <string name="enable_streams_notifications_summary">ߞߊ߬ ߡߊ߬ߝߘߎ߬ߟߌ ߟߎ߬ ߟߊ߫ ߥߦߏ߬ ߞߎߘߊ ߟߎ߫ ߛߏߓߌ߬ߘߐ߬ߓߏ߲ ߠߎ߬ ߟߊߣߊ߬</string>
     <string name="any_network">ߞߙߏ߬ߝߏ߫ ߛߎ߮ ߓߍ߯</string>
     <string name="updates_setting_title">ߟߏ߲ߘߐߦߊߟߌ ߟߎ߬</string>
-    <string name="manual_update_title">ߟߏ߲ߘߐߦߊߟߌ ߟߎ߬ ߡߊߝߍߣߍ߲߫</string>
+    <string name="check_for_updates">ߟߏ߲ߘߐߦߊߟߌ ߟߎ߬ ߡߊߝߍߣߍ߲߫</string>
     <string name="manual_update_description">ߞߊ߬ ߓߐߞߏ߫ ߞߎߘߊ ߟߎ߫ ߕߎ߬ߢߊ߬ߟߐ߲߫ ߓߟߏ ߟߊ߫</string>
     <string name="minimize_on_exit_title">ߞߵߊ߬ ߡߊߖߌ߰ ߟߥߊߟߌߟߊ߲ ߘߐߦߟߍߡߊ߲߫ ߕߎߡߊ</string>
     <string name="minimize_on_exit_none_description">ߝߏߛߌ߬</string>
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 331c3248c..e6daac644 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -410,7 +410,7 @@
     <string name="caption_auto_generated">ସ୍ଵତଃସୃଷ୍ଟ</string>
     <string name="content">ବିଷୟବସ୍ତୁ</string>
     <string name="enable_queue_limit">ଡାଉନଲୋଡ୍ ଧାଡି ସୀମିତ କରନ୍ତୁ</string>
-    <string name="manual_update_title">ଅଦ୍ୟତନ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ</string>
+    <string name="check_for_updates">ଅଦ୍ୟତନ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ</string>
     <string name="manual_update_description">ନୂତନ ସଂସ୍କରଣଗୁଡ଼ିକ ପାଇଁ ମାନୁଆଲ ଯାଞ୍ଚ କରନ୍ତୁ</string>
     <string name="missions_header_pending">ବିଚାରାଧୀନ ଅଛି</string>
     <string name="remove_watched_popup_title">ଦେଖାଯାଇଥିବା ଭିଡିଓଗୁଡିକ ଅପସାରଣ କରିବେ କି\?</string>
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index e34469eb1..196b5b785 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -669,7 +669,7 @@
     <string name="enable_streams_notifications_summary">ਸਬਸਕ੍ਰਾਈਬ ਕੀਤੇ ਚੈਨਲ ਉੱਪਰ ਨਵੀਂ ਸਟ੍ਰੀਮ ਉੱਪਲਬਧ ਹੋਣ ਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਰਾਹੀਂ ਸੂਚਿਤ ਕਰੋ</string>
     <string name="streams_notifications_interval_title">ਜਾਂਚ ਅਵਧੀ</string>
     <string name="any_network">ਕੋਈ ਵੀ ਨੈੱਟਵਰਕ</string>
-    <string name="manual_update_title">ਅੱਪਡੇਟ ਲਈ ਜਾਂਚ ਕਰੋ</string>
+    <string name="check_for_updates">ਅੱਪਡੇਟ ਲਈ ਜਾਂਚ ਕਰੋ</string>
     <string name="low_quality_smaller">ਘੱਟ ਗੁਣਵੱਤਾ (ਛੋਟਾ ਆਕਾਰ)</string>
     <string name="delete_downloaded_files_confirm">ਡਿਸਕ ਤੋਂ ਸਾਰੀਆਂ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਹਟਾਓ\?</string>
     <plurals name="deleted_downloads_toast">
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 0212da91f..6fd61b80e 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -673,7 +673,7 @@
     <string name="processing_may_take_a_moment">Przetwarzanie… Może to chwilę potrwać</string>
     <string name="checking_updates_toast">Sprawdzanie aktualizacji…</string>
     <string name="manual_update_description">Ręcznie sprawdź dostępność nowych wersji</string>
-    <string name="manual_update_title">Sprawdź dostępność aktualizacji</string>
+    <string name="check_for_updates">Sprawdź dostępność aktualizacji</string>
     <string name="feed_new_items">Nowe pozycje kanału</string>
     <string name="show_crash_the_player_summary">Pokazuje opcję psucia podczas korzystania z odtwarzacza</string>
     <string name="crash_the_player">Zepsuj odtwarzacz</string>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index c0c20636b..1d241de51 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -664,7 +664,7 @@
     <string name="remote_search_suggestions">Sugestões de busca remotas</string>
     <string name="local_search_suggestions">Sugestões de busca locais</string>
     <string name="processing_may_take_a_moment">Processando… Pode demorar um pouco</string>
-    <string name="manual_update_title">Procurar por atualizações</string>
+    <string name="check_for_updates">Procurar por atualizações</string>
     <string name="manual_update_description">Procurar manualmente por novas versões</string>
     <string name="checking_updates_toast">Procurando por atualizações…</string>
     <string name="crash_the_player">Travar o reprodutor de vídeo</string>
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 3a6fece0a..325c79d3c 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -664,7 +664,7 @@
     <string name="enqueued_next">Enfileirado o próximo</string>
     <string name="enqueue_next_stream">Pôr na fila o próximo</string>
     <string name="processing_may_take_a_moment">A processar… Pode demorar um momento</string>
-    <string name="manual_update_title">Procurar atualizações</string>
+    <string name="check_for_updates">Procurar atualizações</string>
     <string name="manual_update_description">Verificar manualmente se existe uma nova versão</string>
     <string name="checking_updates_toast">A procurar atualizações…</string>
     <string name="feed_new_items">Novos itens</string>
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 554082a10..e69f79a68 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -664,7 +664,7 @@
     <string name="enqueued_next">Seguinte colocado na fila</string>
     <string name="enqueue_next_stream">Colocar seguinte na fila</string>
     <string name="processing_may_take_a_moment">A processar… Pode levar algum tempo</string>
-    <string name="manual_update_title">Procurar atualizações</string>
+    <string name="check_for_updates">Procurar atualizações</string>
     <string name="manual_update_description">Verificar manualmente se existe uma nova versão</string>
     <string name="checking_updates_toast">A procurar atualizações…</string>
     <string name="feed_new_items">Novos itens</string>
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 49a6b592b..ccc25dc4b 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -664,7 +664,7 @@
     <string name="show_image_indicators_title">Afișați indicatorii de imagine</string>
     <string name="disable_media_tunneling_summary">Dezactivați tunelarea media dacă întâmpinați un ecran negru sau blocaje la redarea video.</string>
     <string name="processing_may_take_a_moment">Procesarea.. Poate dura un moment</string>
-    <string name="manual_update_title">Verifică dacă există actualizări</string>
+    <string name="check_for_updates">Verifică dacă există actualizări</string>
     <string name="manual_update_description">Verifică manual dacă există versiuni noi</string>
     <string name="detail_pinned_comment_view_description">Comentariu lipit</string>
     <string name="error_report_channel_name">Notificare cu raport de eroare</string>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index bd7e5ce20..a885631eb 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -675,7 +675,7 @@
     <string name="enqueue_next_stream">Добавить следующим</string>
     <string name="processing_may_take_a_moment">Обработка… Подождите немного</string>
     <string name="manual_update_description">Проверить обновления вручную</string>
-    <string name="manual_update_title">Проверить обновления</string>
+    <string name="check_for_updates">Проверить обновления</string>
     <string name="checking_updates_toast">Проверка обновлений…</string>
     <string name="feed_new_items">Новое на канале</string>
     <string name="notifications">Уведомления</string>
diff --git a/app/src/main/res/values-ryu/strings.xml b/app/src/main/res/values-ryu/strings.xml
index a531d14bc..195eb22b3 100644
--- a/app/src/main/res/values-ryu/strings.xml
+++ b/app/src/main/res/values-ryu/strings.xml
@@ -651,7 +651,7 @@
     <string name="processing_may_take_a_moment">しーょりちゅう… くーてーんじがんがかかいんかむしりやびらん</string>
     <string name="manual_update_description">みーさるバージョンしーゅどうでぃかくにんさびーん</string>
     <string name="checking_updates_toast">アップデートかくにんちゅう…</string>
-    <string name="manual_update_title">アップデートかくにん</string>
+    <string name="check_for_updates">アップデートかくにん</string>
     <string name="enqueue_next_stream">ちぎキューんかいちちが</string>
     <string name="enqueued_next">ちぎキューんかいちいからさびたん</string>
     <string name="detail_heart_img_view_description">クリエイターぬちむくみてぃ</string>
diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml
index b39a041ee..a497844ab 100644
--- a/app/src/main/res/values-sc/strings.xml
+++ b/app/src/main/res/values-sc/strings.xml
@@ -652,7 +652,7 @@
     <string name="enqueued_next">Postu in lista comente imbeniente</string>
     <string name="enqueue_next_stream">Pone in lista comente imbeniente</string>
     <string name="processing_may_take_a_moment">Protzessende… Bi diat pòdere chèrrere unu pagu de tempus</string>
-    <string name="manual_update_title">Chirca agiornamentos</string>
+    <string name="check_for_updates">Chirca agiornamentos</string>
     <string name="manual_update_description">Verìfica in manera manuale pro versiones noas</string>
     <string name="checking_updates_toast">Controllende sos agiornamentos…</string>
     <string name="error_report_notification_title">NewPipe at rilevadu un\'errore, toca pro lu sinnalare</string>
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 231de5fc4..443809526 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -664,7 +664,7 @@
     <string name="enqueue_next_stream">Pridať do zoznamu</string>
     <string name="enqueued_next">Ďaľší v poradí</string>
     <string name="processing_may_take_a_moment">Spracovávanie... môže to chvíľku trvať</string>
-    <string name="manual_update_title">Skontrolovať aktualizácie</string>
+    <string name="check_for_updates">Skontrolovať aktualizácie</string>
     <string name="manual_update_description">Ručne skontrolovať nové verzie</string>
     <string name="checking_updates_toast">Kontrolujú sa aktualizácie…</string>
     <string name="feed_new_items">Nové položky informačného kanála</string>
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index fc68ce5eb..596fbe263 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -715,7 +715,7 @@
     <string name="duplicate_in_playlist">Плејлисте које су затамњене већ садрже ову ставку.</string>
     <string name="leak_canary_not_available">LeakCanary није доступан</string>
     <string name="app_update_available_notification_text">Додирните да бисте преузели %s</string>
-    <string name="manual_update_title">Провери ажурирања</string>
+    <string name="check_for_updates">Провери ажурирања</string>
     <string name="seekbar_preview_thumbnail_title">Преглед сличице траке за претрагу</string>
     <string name="feed_hide_streams_title">Прикажи следеће стримове</string>
     <string name="feed_show_hide_streams">Прикажи/сакриј стримове</string>
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index be8e2894c..a5c9aaf2b 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -652,7 +652,7 @@
     <string name="remote_search_suggestions">Förslag via fjärrsökning</string>
     <string name="start_main_player_fullscreen_summary">Starta inte videor i minispelaren, utan byt till helskärmsläge direkt, om automatisk rotation är låst. Du kan fortfarande komma åt minispelaren genom att gå ut ur helskärmsläge</string>
     <string name="show_image_indicators_summary">Visa Picasso färgade band ovanpå bilderna som anger deras källa: rött för nätverk, blått för disk och grönt för minne</string>
-    <string name="manual_update_title">Sök efter uppdateringar</string>
+    <string name="check_for_updates">Sök efter uppdateringar</string>
     <string name="manual_update_description">Kolla manuellt efter nya versioner</string>
     <string name="checking_updates_toast">Söker efter uppdateringar…</string>
     <string name="feed_new_items">Nya flödes objekt</string>
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index dfac4fb47..fd12b6a35 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -651,7 +651,7 @@
     <string name="enqueue_next_stream">Sonrakini sıraya ekle</string>
     <string name="enqueued_next">Sonraki sıraya eklendi</string>
     <string name="processing_may_take_a_moment">İşleniyor… Biraz zaman alabilir</string>
-    <string name="manual_update_title">Güncellemeleri denetle</string>
+    <string name="check_for_updates">Güncellemeleri denetle</string>
     <string name="manual_update_description">Yeni sürümleri el ile denetleyin</string>
     <string name="checking_updates_toast">Güncellemeler denetleniyor…</string>
     <string name="feed_new_items">Yeni akış ögeleri</string>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 9348c448d..1227d7db4 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -668,7 +668,7 @@
     <string name="enqueued_next">Заплановано наступним</string>
     <string name="enqueue_next_stream">Запланувати наступним</string>
     <string name="processing_may_take_a_moment">Обробка… Трохи заждіть</string>
-    <string name="manual_update_title">Перевірити наявність оновлень</string>
+    <string name="check_for_updates">Перевірити наявність оновлень</string>
     <string name="manual_update_description">Перевірка нових версій вручну</string>
     <string name="checking_updates_toast">Перевірка оновлень…</string>
     <string name="feed_new_items">Нові записи стрічки</string>
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index cdd6f6e31..393d3cdba 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -648,7 +648,7 @@
     <string name="show_crash_the_player_summary">Hiện tùy chọn dừng đột ngột khi sử dụng trình phát</string>
     <string name="show_error_snackbar">Hiện thanh báo lỗi</string>
     <string name="create_error_notification">Tạo thông báo lỗi</string>
-    <string name="manual_update_title">Kiểm tra cập nhật</string>
+    <string name="check_for_updates">Kiểm tra cập nhật</string>
     <string name="manual_update_description">Kiểm tra phiên bản mới theo cách thủ công</string>
     <string name="checking_updates_toast">Đang kiểm tra cập nhật…</string>
     <string name="feed_new_items">Mục nguồn cấp dữ liệu mới</string>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 89c215943..29b1fc302 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -642,7 +642,7 @@
     <string name="processing_may_take_a_moment">处理中…可能需要一些时间</string>
     <string name="manual_update_description">手动检查新版本</string>
     <string name="checking_updates_toast">检查更新中…</string>
-    <string name="manual_update_title">检查更新</string>
+    <string name="check_for_updates">检查更新</string>
     <string name="feed_new_items">新订阅源条目</string>
     <string name="show_crash_the_player_title">显示\"使播放器崩溃\"</string>
     <string name="show_crash_the_player_summary">在使用播放器时显示一个崩溃选项</string>
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index c91731314..f293773bd 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -288,7 +288,7 @@
     <string name="playback_tempo">節奏</string>
     <string name="playback_speed_control">播放速度控掣</string>
     <string name="systems_language">系統預設</string>
-    <string name="manual_update_title">檢查有冇更新</string>
+    <string name="check_for_updates">檢查有冇更新</string>
     <string name="app_language_title">個 App 用咩語言</string>
     <string name="remove_watched">睇咗嗰啲剷咗佢</string>
     <string name="remove_watched_popup_title">係咪要剷走睇咗嘅影片?</string>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index f2eb503ab..cb9279c41 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -640,7 +640,7 @@
     <string name="enqueued_next">已將下一個加入佇列</string>
     <string name="enqueue_next_stream">將下一個加入佇列</string>
     <string name="processing_may_take_a_moment">正在處理……可能需要一點時間</string>
-    <string name="manual_update_title">檢查更新</string>
+    <string name="check_for_updates">檢查更新</string>
     <string name="manual_update_description">手動檢查新版本</string>
     <string name="checking_updates_toast">正在檢查更新……</string>
     <string name="feed_new_items">新 feed 項目</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b9880282f..5433c88c7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -556,7 +556,8 @@
     <!-- Updates Settings -->
     <string name="updates_setting_title">Updates</string>
     <string name="updates_setting_description">Show a notification to prompt app update when a new version is available</string>
-    <string name="manual_update_title">Check for updates</string>
+    <string name="check_for_updates">Check for updates</string>
+    <string name="manual_update_title" translatable="false">@string/check_for_updates</string>
     <string name="manual_update_description">Manually check for new versions</string>
     <!-- Minimize to exit action -->
     <string name="minimize_on_exit_title">Minimize on app switch</string>

From a3bbbf03b411be091c85b01ba3317d7d613f38fd Mon Sep 17 00:00:00 2001
From: Tobi <TobiGr@users.noreply.github.com>
Date: Wed, 27 Mar 2024 21:27:20 +0100
Subject: [PATCH 116/141] Ask for consent before starting update checks

NewPipe is contacting its servers without asking for the users' consent. This is categorized as "tracking" by F-Droid (see https://github.com/TeamNewPipe/NewPipe/discussions/10785).

This commit disables checking for udpates by default and adds a dialog asking for the user's consent to automatically check for updates if the app version is eligible for them. After upgrading to a version containing this commit the user is asked directly on the first app start. On fresh installs however, showing it on the first app start contributes to a bad onboarding an welcoming experience. Therefore, the dialog is shown at the second app start.

Co-authored-by: Stypox <stypox@pm.me>
---
 app/src/main/java/org/schabi/newpipe/App.java | 13 ++++++-
 .../java/org/schabi/newpipe/MainActivity.java |  7 ++++
 .../newpipe/settings/NewPipeSettings.java     | 16 +++-----
 .../newpipe/settings/SettingMigrations.java   |  6 +--
 .../settings/UpdateSettingsFragment.java      | 37 +++++++++++++++++++
 app/src/main/res/values/settings_keys.xml     |  1 +
 app/src/main/res/values/strings.xml           |  4 ++
 app/src/main/res/xml/update_settings.xml      |  2 +-
 8 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index ee352ae4a..d92425d20 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -60,6 +60,8 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
 public class App extends Application {
     public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
     private static final String TAG = App.class.toString();
+
+    private boolean isFirstRun = false;
     private static App app;
 
     @NonNull
@@ -85,7 +87,13 @@ public class App extends Application {
             return;
         }
 
-        // Initialize settings first because others inits can use its values
+        // check if the last used preference version is set
+        // to determine whether this is the first app run
+        final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(this)
+                .getInt(getString(R.string.last_used_preferences_version), -1);
+        isFirstRun = lastUsedPrefVersion == -1;
+
+        // Initialize settings first because other initializations can use its values
         NewPipeSettings.initSettings(this);
 
         NewPipe.init(getDownloader(),
@@ -255,4 +263,7 @@ public class App extends Application {
         return false;
     }
 
+    public boolean isFirstRun() {
+        return isFirstRun;
+    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index a096e7eaf..346084b62 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -79,6 +79,7 @@ import org.schabi.newpipe.player.Player;
 import org.schabi.newpipe.player.event.OnKeyDownListener;
 import org.schabi.newpipe.player.helper.PlayerHolder;
 import org.schabi.newpipe.player.playqueue.PlayQueue;
+import org.schabi.newpipe.settings.UpdateSettingsFragment;
 import org.schabi.newpipe.util.Constants;
 import org.schabi.newpipe.util.DeviceUtils;
 import org.schabi.newpipe.util.KioskTranslator;
@@ -86,6 +87,7 @@ import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PeertubeHelper;
 import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ReleaseVersionUtil;
 import org.schabi.newpipe.util.SerializedCache;
 import org.schabi.newpipe.util.ServiceHelper;
 import org.schabi.newpipe.util.StateSaver;
@@ -167,6 +169,11 @@ public class MainActivity extends AppCompatActivity {
             // if this is enabled by the user.
             NotificationWorker.initialize(this);
         }
+        if (!UpdateSettingsFragment.wasUserAskedForConsent(this)
+                && ReleaseVersionUtil.INSTANCE.isReleaseApk()
+                && !App.getApp().isFirstRun()) {
+            UpdateSettingsFragment.askForConsentToUpdateChecks(this);
+        }
     }
 
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
index f280324cf..421440ea7 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
@@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.preference.PreferenceManager;
 
+import org.schabi.newpipe.App;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.util.DeviceUtils;
 
@@ -44,14 +45,8 @@ public final class NewPipeSettings {
     private NewPipeSettings() { }
 
     public static void initSettings(final Context context) {
-        // check if the last used preference version is set
-        // to determine whether this is the first app run
-        final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(context)
-                .getInt(context.getString(R.string.last_used_preferences_version), -1);
-        final boolean isFirstRun = lastUsedPrefVersion == -1;
-
         // first run migrations, then setDefaultValues, since the latter requires the correct types
-        SettingMigrations.runMigrationsIfNeeded(context, isFirstRun);
+        SettingMigrations.runMigrationsIfNeeded(context);
 
         // readAgain is true so that if new settings are added their default value is set
         PreferenceManager.setDefaultValues(context, R.xml.main_settings, true);
@@ -68,7 +63,7 @@ public final class NewPipeSettings {
         saveDefaultVideoDownloadDirectory(context);
         saveDefaultAudioDownloadDirectory(context);
 
-        disableMediaTunnelingIfNecessary(context, isFirstRun);
+        disableMediaTunnelingIfNecessary(context);
     }
 
     static void saveDefaultVideoDownloadDirectory(final Context context) {
@@ -146,8 +141,7 @@ public final class NewPipeSettings {
                 R.string.show_remote_search_suggestions_key);
     }
 
-    private static void disableMediaTunnelingIfNecessary(@NonNull final Context context,
-                                                         final boolean isFirstRun) {
+    private static void disableMediaTunnelingIfNecessary(@NonNull final Context context) {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         final String disabledTunnelingKey = context.getString(R.string.disable_media_tunneling_key);
         final String disabledTunnelingAutomaticallyKey =
@@ -162,7 +156,7 @@ public final class NewPipeSettings {
                 prefs.getInt(disabledTunnelingAutomaticallyKey, -1) == 0
                         && !prefs.getBoolean(disabledTunnelingKey, false);
 
-        if (Boolean.TRUE.equals(isFirstRun)
+        if (App.getApp().isFirstRun()
                 || (wasDeviceBlacklistUpdated && !wasMediaTunnelingEnabledByUser)) {
             setMediaTunneling(context);
         }
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
index b7bafde75..d731f2f5e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
@@ -7,6 +7,7 @@ import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.preference.PreferenceManager;
 
+import org.schabi.newpipe.App;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.ErrorUtil;
@@ -163,15 +164,14 @@ public final class SettingMigrations {
     private static final int VERSION = 6;
 
 
-    public static void runMigrationsIfNeeded(@NonNull final Context context,
-                                             final boolean isFirstRun) {
+    public static void runMigrationsIfNeeded(@NonNull final Context context) {
         // setup migrations and check if there is something to do
         sp = PreferenceManager.getDefaultSharedPreferences(context);
         final String lastPrefVersionKey = context.getString(R.string.last_used_preferences_version);
         final int lastPrefVersion = sp.getInt(lastPrefVersionKey, 0);
 
         // no migration to run, already up to date
-        if (isFirstRun) {
+        if (App.getApp().isFirstRun()) {
             sp.edit().putInt(lastPrefVersionKey, VERSION).apply();
             return;
         } else if (lastPrefVersion == VERSION) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
index d1a379e66..b8d0aa556 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java
@@ -1,9 +1,12 @@
 package org.schabi.newpipe.settings;
 
+import android.app.AlertDialog;
+import android.content.Context;
 import android.os.Bundle;
 import android.widget.Toast;
 
 import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
 
 import org.schabi.newpipe.NewVersionWorker;
 import org.schabi.newpipe.R;
@@ -36,4 +39,38 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
         findPreference(getString(R.string.manual_update_key))
                 .setOnPreferenceClickListener(manualUpdateClick);
     }
+
+    public static void askForConsentToUpdateChecks(final Context context) {
+        new AlertDialog.Builder(context)
+                .setTitle(context.getString(R.string.check_for_updates))
+                .setMessage(context.getString(R.string.auto_update_check_description))
+                .setPositiveButton(context.getString(R.string.yes), (d, w) -> {
+                    d.dismiss();
+                    setAutoUpdateCheckEnabled(context, true);
+                })
+                .setNegativeButton(R.string.no, (d, w) -> {
+                    d.dismiss();
+                    // set explicitly to false, since the default is true on previous versions
+                    setAutoUpdateCheckEnabled(context, false);
+                })
+                .show();
+    }
+
+    private static void setAutoUpdateCheckEnabled(final Context context, final boolean enabled) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(context.getString(R.string.update_app_key), enabled)
+                .putBoolean(context.getString(R.string.update_check_consent_key), true)
+                .apply();
+    }
+
+    /**
+     * Whether the user was asked for consent to automatically check for app updates.
+     * @param context
+     * @return true if the user was asked for consent, false otherwise
+     */
+    public static boolean wasUserAskedForConsent(final Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(context.getString(R.string.update_check_consent_key), false);
+    }
 }
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index 3087d1fc2..fb68a464d 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -494,6 +494,7 @@
     </string-array>
 
     <!-- Updates -->
+    <string name="update_check_consent_key">update_check_consent_key</string>
     <string name="update_app_key">update_app_key</string>
     <string name="manual_update_key">manual_update_key</string>
     <string name="update_pref_screen_key">update_pref_screen_key</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5433c88c7..c4ad8b1d9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,6 +7,8 @@
     <string name="install">Install</string>
     <string name="cancel">Cancel</string>
     <string name="ok">OK</string>
+    <string name="yes">Yes</string>
+    <string name="no">No</string>
     <string name="open_in_browser">Open in browser</string>
     <string name="mark_as_watched">Mark as watched</string>
     <string name="open_in_popup_mode">Open in popup mode</string>
@@ -557,8 +559,10 @@
     <string name="updates_setting_title">Updates</string>
     <string name="updates_setting_description">Show a notification to prompt app update when a new version is available</string>
     <string name="check_for_updates">Check for updates</string>
+    <string name="auto_update_check_description">NewPipe can automatically check for new versions from time to time and notify you once they are available.\nDo you want to enable this?</string>
     <string name="manual_update_title" translatable="false">@string/check_for_updates</string>
     <string name="manual_update_description">Manually check for new versions</string>
+
     <!-- Minimize to exit action -->
     <string name="minimize_on_exit_title">Minimize on app switch</string>
     <string name="minimize_on_exit_summary">Action when switching to other app from main video player — %s</string>
diff --git a/app/src/main/res/xml/update_settings.xml b/app/src/main/res/xml/update_settings.xml
index a44555edf..becd5c5c9 100644
--- a/app/src/main/res/xml/update_settings.xml
+++ b/app/src/main/res/xml/update_settings.xml
@@ -4,7 +4,7 @@
     android:title="@string/settings_category_updates_title">
 
     <SwitchPreferenceCompat
-        android:defaultValue="true"
+        android:defaultValue="false"
         android:key="@string/update_app_key"
         android:summary="@string/updates_setting_description"
         android:title="@string/updates_setting_title"

From 3703fed1a52c35ddcbea83494fba1638a3bf1a7b Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 11:08:33 +0100
Subject: [PATCH 117/141] update_app_key default value should be false

---
 app/src/main/java/org/schabi/newpipe/MainActivity.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 346084b62..094eb5197 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -183,7 +183,7 @@ public class MainActivity extends AppCompatActivity {
         final App app = App.getApp();
         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), false)) {
             // Start the worker which is checking all conditions
             // and eventually searching for a new version.
             NewVersionWorker.enqueueNewVersionCheckingWork(app, false);

From 92402685f81bfc25f37efe4d1d6ff861486c5dd6 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 11:14:30 +0100
Subject: [PATCH 118/141] Improve new version checks before running

---
 app/src/main/java/org/schabi/newpipe/MainActivity.java | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 094eb5197..175694125 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -170,8 +170,8 @@ public class MainActivity extends AppCompatActivity {
             NotificationWorker.initialize(this);
         }
         if (!UpdateSettingsFragment.wasUserAskedForConsent(this)
-                && ReleaseVersionUtil.INSTANCE.isReleaseApk()
-                && !App.getApp().isFirstRun()) {
+                && !App.getApp().isFirstRun()
+                && ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
             UpdateSettingsFragment.askForConsentToUpdateChecks(this);
         }
     }
@@ -183,7 +183,8 @@ public class MainActivity extends AppCompatActivity {
         final App app = App.getApp();
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
 
-        if (prefs.getBoolean(app.getString(R.string.update_app_key), false)) {
+        if (prefs.getBoolean(app.getString(R.string.update_app_key), false)
+                && prefs.getBoolean(app.getString(R.string.update_check_consent_key), false)) {
             // Start the worker which is checking all conditions
             // and eventually searching for a new version.
             NewVersionWorker.enqueueNewVersionCheckingWork(app, false);

From bef5907ec3be2f223369492c35dc3ba63c7f56cb Mon Sep 17 00:00:00 2001
From: bg1722 <bg1722>
Date: Sat, 10 Apr 2021 23:26:20 +0200
Subject: [PATCH 119/141] show OverallDuration in Playlist

earlier only overall amount of videos was shown. Now overall duration is shown there too - as formatted by existing Localization.concatenateStrings() and Localization.getDurationString().

show all videos OverallDuration in local Playlist too

refactor to make implementation in LocalPlaylistFragment and PlaylistFragment more obviously similar

unfortunately could not refactor upto BaseLocalListFragment

revert the changes for online Playlists

because they are paginated and may be infinite i.e. correct count may come only from the service->extractor chain which unfortunately does not give overall duration yet

next try to improve user-experience with online Playlist

just show that duration is longer (">") than the calculated value in case there is more page(s)

even more improve user-experience for online Playlist

by adding the duration of next items as soon as they are made visible

make showing of playlists duration configurable, disabled by default

adjusted duration to be handled as long because it comes as long from extractor

no idea why I handled it as int earlier

Revert "make showing of playlists duration configurable, disabled by default", refactor

This reverts commit bc1ba17a20d3dd1763210f81d7ca67c5f1734a3d.

Fix rebase

Apply review

Rename video -> stream

Remove unused settings keys
---
 .../list/playlist/PlaylistFragment.java       | 29 +++++++++++++++++--
 .../local/playlist/LocalPlaylistFragment.java | 24 ++++++++++-----
 .../org/schabi/newpipe/util/Localization.java | 21 +++++++++++++-
 3 files changed, 64 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index ab3963d61..998ea0624 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -89,6 +89,9 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
 
     private MenuItem playlistBookmarkButton;
 
+    private long streamCount;
+    private long playlistOverallDurationSeconds;
+
     public static PlaylistFragment getInstance(final int serviceId, final String url,
                                                final String name) {
         final PlaylistFragment instance = new PlaylistFragment();
@@ -277,6 +280,12 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
         animate(headerBinding.uploaderLayout, false, 200);
     }
 
+    @Override
+    public void handleNextItems(final ListExtractor.InfoItemsPage result) {
+        super.handleNextItems(result);
+        setStreamCountAndOverallDuration(result.getItems(), !result.hasNextPage());
+    }
+
     @Override
     public void handleResult(@NonNull final PlaylistInfo result) {
         super.handleResult(result);
@@ -322,8 +331,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
                     .into(headerBinding.uploaderAvatarView);
         }
 
-        headerBinding.playlistStreamCount.setText(Localization
-                .localizeStreamCount(getContext(), result.getStreamCount()));
+        streamCount = result.getStreamCount();
+        setStreamCountAndOverallDuration(result.getRelatedItems(), !result.hasNextPage());
 
         final Description description = result.getDescription();
         if (description != null && description != Description.EMPTY_DESCRIPTION
@@ -486,4 +495,20 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
         playlistBookmarkButton.setIcon(drawable);
         playlistBookmarkButton.setTitle(titleRes);
     }
+
+    private void setStreamCountAndOverallDuration(final List<StreamInfoItem> list,
+                                                  final boolean isDurationComplete) {
+        if (activity != null && headerBinding != null) {
+            playlistOverallDurationSeconds += list.stream()
+                    .mapToLong(x -> x.getDuration())
+                    .sum();
+            headerBinding.playlistStreamCount.setText(
+                Localization.concatenateStrings(
+                    Localization.localizeStreamCount(activity, streamCount),
+                    Localization.getDurationString(playlistOverallDurationSeconds,
+                            isDurationComplete))
+            );
+        }
+    }
+
 }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index 51da52ae0..c4fe3b896 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -502,7 +502,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                     }
 
                     final long videoCount = itemListAdapter.getItemsList().size();
-                    setVideoCount(videoCount);
+                    setStreamCountAndOverallDuration(itemListAdapter.getItemsList());
                     if (videoCount == 0) {
                         showEmptyState();
                     }
@@ -532,7 +532,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
             itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
             itemsListState = null;
         }
-        setVideoCount(itemListAdapter.getItemsList().size());
+        setStreamCountAndOverallDuration(itemListAdapter.getItemsList());
 
         PlayButtonHelper.initPlaylistControlClickListener(activity, playlistControlBinding, this);
 
@@ -665,7 +665,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
                 .subscribe(itemsToKeep -> {
                     itemListAdapter.clearStreamItemList();
                     itemListAdapter.addItems(itemsToKeep);
-                    setVideoCount(itemListAdapter.getItemsList().size());
+                    setStreamCountAndOverallDuration(itemListAdapter.getItemsList());
                     saveChanges();
 
                     hideLoading();
@@ -684,7 +684,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
             updateThumbnailUrl();
         }
 
-        setVideoCount(itemListAdapter.getItemsList().size());
+        setStreamCountAndOverallDuration(itemListAdapter.getItemsList());
         saveChanges();
     }
 
@@ -855,10 +855,20 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
         this.name = !TextUtils.isEmpty(title) ? title : "";
     }
 
-    private void setVideoCount(final long count) {
+    private void setStreamCountAndOverallDuration(final ArrayList<LocalItem> itemsList) {
         if (activity != null && headerBinding != null) {
-            headerBinding.playlistStreamCount.setText(Localization
-                    .localizeStreamCount(activity, count));
+            final long streamCount = itemsList.size();
+            final long playlistOverallDurationSeconds = itemsList.stream()
+                    .filter(PlaylistStreamEntry.class::isInstance)
+                    .map(PlaylistStreamEntry.class::cast)
+                    .map(PlaylistStreamEntry::getStreamEntity)
+                    .mapToLong(StreamEntity::getDuration)
+                    .sum();
+            headerBinding.playlistStreamCount.setText(
+                    Localization.concatenateStrings(
+                            Localization.localizeStreamCount(activity, streamCount),
+                            Localization.getDurationString(playlistOverallDurationSeconds))
+            );
         }
     }
 
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 0485413cc..5d73d21f0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -238,7 +238,25 @@ public final class Localization {
         }
     }
 
+    /**
+     * Get a readable text for a duration in the format {@code days:hours:minutes:seconds}.
+     * Prepended zeros are removed.
+     * @param duration the duration in seconds
+     * @return a formatted duration String or {@code 0:00} if the duration is zero.
+     */
     public static String getDurationString(final long duration) {
+        return getDurationString(duration, true);
+    }
+
+    /**
+     * Get a readable text for a duration in the format {@code days:hours:minutes:seconds+}.
+     * Prepended zeros are removed. If the given duration is incomplete, a plus is appended to the
+     * duration string.
+     * @param duration the duration in seconds
+     * @param isDurationComplete whether the given duration is complete or whether info is missing
+     * @return a formatted duration String or {@code 0:00} if the duration is zero.
+     */
+    public static String getDurationString(final long duration, final boolean isDurationComplete) {
         final String output;
 
         final long days = duration / (24 * 60 * 60L); /* greater than a day */
@@ -256,7 +274,8 @@ public final class Localization {
         } else {
             output = String.format(Locale.US, "%d:%02d", minutes, seconds);
         }
-        return output;
+        final String durationPostfix = isDurationComplete ? "" : "+";
+        return output + durationPostfix;
     }
 
     /**

From 4591c096378eb7254092e0692cf76852ceaed766 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 17:59:28 +0100
Subject: [PATCH 120/141] Apply review

---
 .../schabi/newpipe/database/Migrations.java   | 19 ++--
 .../playlist/PlaylistDuplicatesEntry.java     |  1 +
 .../database/playlist/PlaylistLocalItem.java  | 74 ---------------
 .../playlist/dao/PlaylistRemoteDAO.java       |  2 +-
 .../playlist/dao/PlaylistStreamDAO.java       | 22 +----
 .../local/bookmark/BookmarkFragment.java      | 24 ++---
 .../local/bookmark/MergedPlaylistManager.java | 95 +++++++++++++++++++
 .../local/playlist/LocalPlaylistManager.java  | 15 +--
 .../local/playlist/RemotePlaylistManager.java |  6 +-
 .../settings/SelectPlaylistFragment.java      |  6 +-
 10 files changed, 123 insertions(+), 141 deletions(-)
 create mode 100644 app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java

diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index fa470c2f2..9d641965d 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -258,19 +258,19 @@ public final class Migrations {
                         + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                         + "`name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, "
                         + "`thumbnail_stream_id` INTEGER NOT NULL, "
-                        + "`display_index` INTEGER NOT NULL DEFAULT 0)");
+                        + "`display_index` INTEGER NOT NULL)");
                 database.execSQL("INSERT INTO `playlists_tmp` "
-                        + "(`uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`) "
-                        + "SELECT `uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id` "
+                        + "(`uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
+                        + "`display_index`) "
+                        + "SELECT `uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
+                        + "-1 "
                         + "FROM `playlists`");
 
-                // Replace the old table.
+                // Replace the old table, note that this also removes the index on the name which
+                // we don't need anymore.
                 database.execSQL("DROP TABLE `playlists`");
                 database.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`");
 
-                // Create index on the new table.
-                database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
-
 
                 // Update remote_playlists.
                 // Create a temp table to initialize display_index.
@@ -285,13 +285,12 @@ public final class Migrations {
                         + "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
                         + "`stream_count` FROM `remote_playlists`");
 
-                // Replace the old table.
+                // Replace the old table, note that this also removes the index on the name which
+                // we don't need anymore.
                 database.execSQL("DROP TABLE `remote_playlists`");
                 database.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`");
 
                 // Create index on the new table.
-                database.execSQL("CREATE INDEX `index_remote_playlists_name` "
-                        + "ON `remote_playlists` (`name`)");
                 database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
                         + "ON `remote_playlists` (`service_id`, `url`)");
 
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java
index dcd3b2b6c..3be85e6e1 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistDuplicatesEntry.java
@@ -13,6 +13,7 @@ public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry {
     @ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
     public final long timesStreamIsContained;
 
+    @SuppressWarnings("checkstyle:ParameterNumber")
     public PlaylistDuplicatesEntry(final long uid,
                                    final String name,
                                    final String thumbnailUrl,
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
index efd7120d3..072c49e2c 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistLocalItem.java
@@ -1,12 +1,6 @@
 package org.schabi.newpipe.database.playlist;
 
 import org.schabi.newpipe.database.LocalItem;
-import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
 
 public interface PlaylistLocalItem extends LocalItem {
     String getOrderingName();
@@ -16,72 +10,4 @@ public interface PlaylistLocalItem extends LocalItem {
     long getUid();
 
     void setDisplayIndex(long displayIndex);
-
-    /**
-     * Merge localPlaylists and remotePlaylists by the display index.
-     * If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
-     *
-     * @param localPlaylists  local playlists
-     * @param remotePlaylists remote playlists
-     * @return merged playlists
-     */
-    static List<PlaylistLocalItem> merge(
-            final List<PlaylistMetadataEntry> localPlaylists,
-            final List<PlaylistRemoteEntity> remotePlaylists) {
-        Collections.sort(localPlaylists,
-                Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
-        Collections.sort(remotePlaylists,
-                Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
-
-        // This algorithm is similar to the merge operation in merge sort.
-
-        final List<PlaylistLocalItem> result = new ArrayList<>(
-                localPlaylists.size() + remotePlaylists.size());
-        final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
-
-        int i = 0;
-        int j = 0;
-        while (i < localPlaylists.size()) {
-            while (j < remotePlaylists.size()) {
-                if (remotePlaylists.get(j).getDisplayIndex()
-                        <= localPlaylists.get(i).getDisplayIndex()) {
-                    addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
-                    j++;
-                } else {
-                    break;
-                }
-            }
-            addItem(result, localPlaylists.get(i), itemsWithSameIndex);
-            i++;
-        }
-        while (j < remotePlaylists.size()) {
-            addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
-            j++;
-        }
-        addItemsWithSameIndex(result, itemsWithSameIndex);
-
-        return result;
-    }
-
-    static void addItem(final List<PlaylistLocalItem> result, final PlaylistLocalItem item,
-                        final List<PlaylistLocalItem> itemsWithSameIndex) {
-        if (!itemsWithSameIndex.isEmpty()
-                && itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
-            // The new item has a different display index, add previous items with same
-            // index to the result.
-            addItemsWithSameIndex(result, itemsWithSameIndex);
-            itemsWithSameIndex.clear();
-        }
-        itemsWithSameIndex.add(item);
-    }
-
-    static void addItemsWithSameIndex(final List<PlaylistLocalItem> result,
-                                      final List<PlaylistLocalItem> itemsWithSameIndex) {
-        if (itemsWithSameIndex.size() > 1) {
-            Collections.sort(itemsWithSameIndex,
-                    Comparator.comparing(PlaylistLocalItem::getOrderingName,
-                            Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
-        }
-        result.addAll(itemsWithSameIndex);
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
index 8118bc40f..8ab8a2afd 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistRemoteDAO.java
@@ -42,7 +42,7 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
 
     @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
      + " ORDER BY " + REMOTE_PLAYLIST_DISPLAY_INDEX)
-    Flowable<List<PlaylistRemoteEntity>> getDisplayIndexOrderedPlaylists();
+    Flowable<List<PlaylistRemoteEntity>> getPlaylists();
 
     @Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
             + " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
index d795e6ea7..4e1c163a4 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
@@ -92,26 +92,6 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
             + " ORDER BY " + JOIN_INDEX + " ASC")
     Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
 
-    @Transaction
-    @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", "
-            + PLAYLIST_THUMBNAIL_PERMANENT + ", " + PLAYLIST_THUMBNAIL_STREAM_ID + ", "
-            + PLAYLIST_DISPLAY_INDEX + ", "
-
-            + " CASE WHEN " + PLAYLIST_THUMBNAIL_STREAM_ID + " = "
-            + PlaylistEntity.DEFAULT_THUMBNAIL_ID + " THEN " + "'" + DEFAULT_THUMBNAIL + "'"
-            + " ELSE (SELECT " + STREAM_THUMBNAIL_URL
-            + " FROM " + STREAM_TABLE
-            + " WHERE " + STREAM_TABLE + "." + STREAM_ID + " = " + PLAYLIST_THUMBNAIL_STREAM_ID
-            + " ) END AS " + PLAYLIST_THUMBNAIL_URL + ", "
-
-            + "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
-            + " FROM " + PLAYLIST_TABLE
-            + " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
-            + " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
-            + " GROUP BY " + PLAYLIST_ID
-            + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
-    Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
-
     @Transaction
     @Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", "
             + PLAYLIST_THUMBNAIL_PERMANENT + ", " + PLAYLIST_THUMBNAIL_STREAM_ID + ", "
@@ -130,7 +110,7 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
             + " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
             + " GROUP BY " + PLAYLIST_ID
             + " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
-    Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylistMetadata();
+    Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
 
     @RewriteQueriesToDropUnusedColumns
     @Transaction
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 41acd2615..59e2582ff 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -1,5 +1,6 @@
 package org.schabi.newpipe.local.bookmark;
 
+import static org.schabi.newpipe.local.bookmark.MergedPlaylistManager.getMergedOrderedPlaylists;
 import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
 
 import android.content.DialogInterface;
@@ -47,7 +48,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import icepick.State;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Flowable;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import io.reactivex.rxjava3.disposables.Disposable;
 
@@ -184,9 +184,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         }
         isLoadingComplete.set(false);
 
-        Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
-                        remotePlaylistManager.getDisplayIndexOrderedPlaylists(),
-                        PlaylistLocalItem::merge)
+        getMergedOrderedPlaylists(localPlaylistManager, remotePlaylistManager)
                 .onBackpressureLatest()
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(getPlaylistsSubscriber());
@@ -400,16 +398,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
         for (int i = 0; i < items.size(); i++) {
             final LocalItem item = items.get(i);
 
-            if (item instanceof PlaylistMetadataEntry) {
-                if (((PlaylistMetadataEntry) item).getDisplayIndex() != i) {
-                    ((PlaylistMetadataEntry) item).setDisplayIndex(i);
-                    localItemsUpdate.add((PlaylistMetadataEntry) item);
-                }
-            } else if (item instanceof PlaylistRemoteEntity) {
-                if (((PlaylistRemoteEntity) item).getDisplayIndex() != i) {
-                    ((PlaylistRemoteEntity) item).setDisplayIndex(i);
-                    remoteItemsUpdate.add((PlaylistRemoteEntity) item);
-                }
+            if (item instanceof PlaylistMetadataEntry
+                    && ((PlaylistMetadataEntry) item).getDisplayIndex() != i) {
+                ((PlaylistMetadataEntry) item).setDisplayIndex(i);
+                localItemsUpdate.add((PlaylistMetadataEntry) item);
+            } else if (item instanceof PlaylistRemoteEntity
+                    && ((PlaylistRemoteEntity) item).getDisplayIndex() != i) {
+                ((PlaylistRemoteEntity) item).setDisplayIndex(i);
+                remoteItemsUpdate.add((PlaylistRemoteEntity) item);
             }
         }
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java
new file mode 100644
index 000000000..6b0eda132
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java
@@ -0,0 +1,95 @@
+package org.schabi.newpipe.local.bookmark;
+
+import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
+import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
+import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
+import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
+import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import io.reactivex.rxjava3.core.Flowable;
+
+/**
+ * Takes care of remote and local playlists at once, hence "merged".
+ */
+public final class MergedPlaylistManager {
+
+    private MergedPlaylistManager() {
+    }
+
+    public static Flowable<List<PlaylistLocalItem>> getMergedOrderedPlaylists(
+            final LocalPlaylistManager localPlaylistManager,
+            final RemotePlaylistManager remotePlaylistManager) {
+        return Flowable.combineLatest(
+                localPlaylistManager.getPlaylists(),
+                remotePlaylistManager.getPlaylists(),
+                MergedPlaylistManager::merge
+        );
+    }
+
+    /**
+     * Merge localPlaylists and remotePlaylists by the display index.
+     * If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
+     *
+     * @param localPlaylists  local playlists, already sorted by display index
+     * @param remotePlaylists remote playlists, already sorted by display index
+     * @return merged playlists
+     */
+    private static List<PlaylistLocalItem> merge(
+            final List<PlaylistMetadataEntry> localPlaylists,
+            final List<PlaylistRemoteEntity> remotePlaylists) {
+
+        // This algorithm is similar to the merge operation in merge sort.
+        final List<PlaylistLocalItem> result = new ArrayList<>(
+                localPlaylists.size() + remotePlaylists.size());
+        final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
+
+        int i = 0;
+        int j = 0;
+        while (i < localPlaylists.size()) {
+            while (j < remotePlaylists.size()) {
+                if (remotePlaylists.get(j).getDisplayIndex()
+                        <= localPlaylists.get(i).getDisplayIndex()) {
+                    addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
+                    j++;
+                } else {
+                    break;
+                }
+            }
+            addItem(result, localPlaylists.get(i), itemsWithSameIndex);
+            i++;
+        }
+        while (j < remotePlaylists.size()) {
+            addItem(result, remotePlaylists.get(j), itemsWithSameIndex);
+            j++;
+        }
+        addItemsWithSameIndex(result, itemsWithSameIndex);
+
+        return result;
+    }
+
+    private static void addItem(final List<PlaylistLocalItem> result,
+                                final PlaylistLocalItem item,
+                                final List<PlaylistLocalItem> itemsWithSameIndex) {
+        if (!itemsWithSameIndex.isEmpty()
+                && itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
+            // The new item has a different display index, add previous items with same
+            // index to the result.
+            addItemsWithSameIndex(result, itemsWithSameIndex);
+            itemsWithSameIndex.clear();
+        }
+        itemsWithSameIndex.add(item);
+    }
+
+    private static void addItemsWithSameIndex(final List<PlaylistLocalItem> result,
+                                              final List<PlaylistLocalItem> itemsWithSameIndex) {
+        Collections.sort(itemsWithSameIndex,
+                Comparator.comparing(PlaylistLocalItem::getOrderingName,
+                        Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
+        result.addAll(itemsWithSameIndex);
+    }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
index e153f0a10..461ac2d0a 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
@@ -19,7 +19,6 @@ import java.util.List;
 import io.reactivex.rxjava3.core.Completable;
 import io.reactivex.rxjava3.core.Flowable;
 import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class LocalPlaylistManager {
@@ -108,10 +107,6 @@ public class LocalPlaylistManager {
         })).subscribeOn(Schedulers.io());
     }
 
-    public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
-        return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
-    }
-
     public Flowable<List<PlaylistStreamEntry>> getDistinctPlaylistStreams(final long playlistId) {
         return playlistStreamTable
                 .getStreamsWithoutDuplicates(playlistId).subscribeOn(Schedulers.io());
@@ -129,20 +124,14 @@ public class LocalPlaylistManager {
                 .subscribeOn(Schedulers.io());
     }
 
-    public Flowable<List<PlaylistMetadataEntry>> getDisplayIndexOrderedPlaylists() {
-        return playlistStreamTable.getDisplayIndexOrderedPlaylistMetadata()
-                .subscribeOn(Schedulers.io());
+    public Flowable<List<PlaylistMetadataEntry>> getPlaylists() {
+        return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
     }
 
     public Flowable<List<PlaylistStreamEntry>> getPlaylistStreams(final long playlistId) {
         return playlistStreamTable.getOrderedStreamsOf(playlistId).subscribeOn(Schedulers.io());
     }
 
-    public Single<Integer> deletePlaylist(final long playlistId) {
-        return Single.fromCallable(() -> playlistTable.deletePlaylist(playlistId))
-                .subscribeOn(Schedulers.io());
-    }
-
     public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
         return modifyPlaylist(playlistId, name, THUMBNAIL_ID_LEAVE_UNCHANGED, false, -1);
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
index 45d4ef644..4cc51f752 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/RemotePlaylistManager.java
@@ -23,11 +23,7 @@ public class RemotePlaylistManager {
     }
 
     public Flowable<List<PlaylistRemoteEntity>> getPlaylists() {
-        return playlistRemoteTable.getAll().subscribeOn(Schedulers.io());
-    }
-
-    public Flowable<List<PlaylistRemoteEntity>> getDisplayIndexOrderedPlaylists() {
-        return playlistRemoteTable.getDisplayIndexOrderedPlaylists().subscribeOn(Schedulers.io());
+        return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io());
     }
 
     public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index 3e97d42e6..36abef9e5 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -1,5 +1,7 @@
 package org.schabi.newpipe.settings;
 
+import static org.schabi.newpipe.local.bookmark.MergedPlaylistManager.getMergedOrderedPlaylists;
+
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -31,7 +33,6 @@ import java.util.List;
 import java.util.Vector;
 
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Flowable;
 import io.reactivex.rxjava3.disposables.Disposable;
 
 public class SelectPlaylistFragment extends DialogFragment {
@@ -90,8 +91,7 @@ public class SelectPlaylistFragment extends DialogFragment {
         final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database);
         final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database);
 
-        disposable = Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
-                remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
+        disposable = getMergedOrderedPlaylists(localPlaylistManager, remotePlaylistManager)
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(this::displayPlaylists, this::onError);
     }

From 92e9c3e42eed0d8f5f29210e6527f8351edfc4da Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 20:43:55 +0100
Subject: [PATCH 121/141] Fix DatabaseMigrationTest

Complete removal of unneeded index, and remove default value for `remote_playlists.display_index`.
---
 .../9.json                                    | 25 ++--------
 .../newpipe/database/DatabaseMigrationTest.kt | 47 ++++++++++++-------
 .../schabi/newpipe/database/Migrations.java   |  7 +--
 .../playlist/model/PlaylistEntity.java        |  5 +-
 .../playlist/model/PlaylistRemoteEntity.java  |  1 -
 5 files changed, 37 insertions(+), 48 deletions(-)

diff --git a/app/schemas/org.schabi.newpipe.database.AppDatabase/9.json b/app/schemas/org.schabi.newpipe.database.AppDatabase/9.json
index 0fcd383af..aced06c0a 100644
--- a/app/schemas/org.schabi.newpipe.database.AppDatabase/9.json
+++ b/app/schemas/org.schabi.newpipe.database.AppDatabase/9.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 9,
-    "identityHash": "94596ea2227c63dd78b472ea4a83f1c4",
+    "identityHash": "7591e8039faa74d8c0517dc867af9d3e",
     "entities": [
       {
         "tableName": "subscriptions",
@@ -362,17 +362,7 @@
             "uid"
           ]
         },
-        "indices": [
-          {
-            "name": "index_playlists_name",
-            "unique": false,
-            "columnNames": [
-              "name"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
-          }
-        ],
+        "indices": [],
         "foreignKeys": []
       },
       {
@@ -511,15 +501,6 @@
           ]
         },
         "indices": [
-          {
-            "name": "index_remote_playlists_name",
-            "unique": false,
-            "columnNames": [
-              "name"
-            ],
-            "orders": [],
-            "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
-          },
           {
             "name": "index_remote_playlists_service_id_url",
             "unique": true,
@@ -743,7 +724,7 @@
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '94596ea2227c63dd78b472ea4a83f1c4')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7591e8039faa74d8c0517dc867af9d3e')"
     ]
   }
 }
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
index f71880366..a34cfece6 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
+++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt
@@ -122,8 +122,10 @@ class DatabaseMigrationTest {
         )
 
         testHelper.runMigrationsAndValidate(
-            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
-            true, Migrations.MIGRATION_5_6
+            AppDatabase.DATABASE_NAME,
+            Migrations.DB_VER_9,
+            true,
+            Migrations.MIGRATION_8_9
         )
 
         val migratedDatabaseV3 = getMigratedDatabase()
@@ -209,6 +211,11 @@ class DatabaseMigrationTest {
             true, Migrations.MIGRATION_7_8
         )
 
+        testHelper.runMigrationsAndValidate(
+            AppDatabase.DATABASE_NAME, Migrations.DB_VER_9,
+            true, Migrations.MIGRATION_8_9
+        )
+
         val migratedDatabaseV8 = getMigratedDatabase()
         val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
 
@@ -220,25 +227,27 @@ class DatabaseMigrationTest {
 
     @Test
     fun migrateDatabaseFrom8to9() {
-        val databaseInV5 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_5)
+        val databaseInV8 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_8)
 
         val localUid1: Long
         val localUid2: Long
         val remoteUid1: Long
         val remoteUid2: Long
-        databaseInV5.run {
+        databaseInV8.run {
             localUid1 = insert(
                 "playlists", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("name", DEFAULT_NAME + "1")
-                    put("thumbnail_url", DEFAULT_THUMBNAIL)
+                    put("is_thumbnail_permanent", false)
+                    put("thumbnail_stream_id", -1)
                 }
             )
             localUid2 = insert(
                 "playlists", SQLiteDatabase.CONFLICT_FAIL,
                 ContentValues().apply {
                     put("name", DEFAULT_NAME + "2")
-                    put("thumbnail_url", DEFAULT_THUMBNAIL)
+                    put("is_thumbnail_permanent", false)
+                    put("thumbnail_stream_id", -1)
                 }
             )
             delete(
@@ -267,33 +276,35 @@ class DatabaseMigrationTest {
         }
 
         testHelper.runMigrationsAndValidate(
-            AppDatabase.DATABASE_NAME, Migrations.DB_VER_6,
-            true, Migrations.MIGRATION_5_6
+            AppDatabase.DATABASE_NAME,
+            Migrations.DB_VER_9,
+            true,
+            Migrations.MIGRATION_8_9
         )
 
-        val migratedDatabaseV6 = getMigratedDatabase()
-        var localListFromDB = migratedDatabaseV6.playlistDAO().all.blockingFirst()
-        var remoteListFromDB = migratedDatabaseV6.playlistRemoteDAO().all.blockingFirst()
+        val migratedDatabaseV9 = getMigratedDatabase()
+        var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
+        var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
 
         assertEquals(1, localListFromDB.size)
         assertEquals(localUid2, localListFromDB[0].uid)
-        assertEquals(0, localListFromDB[0].displayIndex)
+        assertEquals(-1, localListFromDB[0].displayIndex)
         assertEquals(1, remoteListFromDB.size)
         assertEquals(remoteUid1, remoteListFromDB[0].uid)
-        assertEquals(0, remoteListFromDB[0].displayIndex)
+        assertEquals(-1, remoteListFromDB[0].displayIndex)
 
-        val localUid3 = migratedDatabaseV6.playlistDAO().insert(
-            PlaylistEntity(DEFAULT_NAME + "3", DEFAULT_THUMBNAIL, -1)
+        val localUid3 = migratedDatabaseV9.playlistDAO().insert(
+            PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1)
         )
-        val remoteUid3 = migratedDatabaseV6.playlistRemoteDAO().insert(
+        val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert(
             PlaylistRemoteEntity(
                 DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
                 DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
             )
         )
 
-        localListFromDB = migratedDatabaseV6.playlistDAO().all.blockingFirst()
-        remoteListFromDB = migratedDatabaseV6.playlistRemoteDAO().all.blockingFirst()
+        localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
+        remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
         assertEquals(2, localListFromDB.size)
         assertEquals(localUid3, localListFromDB[1].uid)
         assertEquals(-1, localListFromDB[1].displayIndex)
diff --git a/app/src/main/java/org/schabi/newpipe/database/Migrations.java b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
index 9d641965d..c9f630869 100644
--- a/app/src/main/java/org/schabi/newpipe/database/Migrations.java
+++ b/app/src/main/java/org/schabi/newpipe/database/Migrations.java
@@ -278,12 +278,13 @@ public final class Migrations {
                         + "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                         + "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
                         + "`thumbnail_url` TEXT, `uploader` TEXT, "
-                        + "`display_index` INTEGER NOT NULL DEFAULT 0,"
+                        + "`display_index` INTEGER NOT NULL,"
                         + "`stream_count` INTEGER)");
                 database.execSQL("INSERT INTO `remote_playlists_tmp` (`uid`, `service_id`, "
-                        + "`name`, `url`, `thumbnail_url`, `uploader`, `stream_count`)"
+                        + "`name`, `url`, `thumbnail_url`, `uploader`, `display_index`, "
+                        + "`stream_count`)"
                         + "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
-                        + "`stream_count` FROM `remote_playlists`");
+                        + "-1, `stream_count` FROM `remote_playlists`");
 
                 // Replace the old table, note that this also removes the index on the name which
                 // we don't need anymore.
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
index cb18027d0..e0c1a06b7 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java
@@ -3,17 +3,14 @@ package org.schabi.newpipe.database.playlist.model;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
 import androidx.room.Ignore;
-import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
-import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
 import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
 
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
 
-@Entity(tableName = PLAYLIST_TABLE,
-        indices = {@Index(value = {PLAYLIST_NAME})})
+@Entity(tableName = PLAYLIST_TABLE)
 public class PlaylistEntity {
 
     public static final String DEFAULT_THUMBNAIL = "drawable://"
diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
index 50c3899f1..60027a057 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java
@@ -21,7 +21,6 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE
 
 @Entity(tableName = REMOTE_PLAYLIST_TABLE,
         indices = {
-                @Index(value = {REMOTE_PLAYLIST_NAME}),
                 @Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
         })
 public class PlaylistRemoteEntity implements PlaylistLocalItem {

From e66e1b542c937a2df7e658102580225aededec4b Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 20:55:24 +0100
Subject: [PATCH 122/141] Also sort playlist duplicates by display index

---
 .../schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
index 4e1c163a4..85b891770 100644
--- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
+++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java
@@ -154,6 +154,6 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
             + " AND :streamUrl = :streamUrl"
 
             + " GROUP BY " + JOIN_PLAYLIST_ID
-            + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
+            + " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
     Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicatesMetadata(String streamUrl);
 }

From 90979e2a8130655aacaa36b024816b832fbcbc06 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Fri, 29 Mar 2024 20:58:07 +0100
Subject: [PATCH 123/141] Fix PlaylistLocalItemTest

---
 .../local/bookmark/MergedPlaylistManager.java |  2 +-
 .../playlist/PlaylistLocalItemTest.java       | 25 ++++++++++---------
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java
index 6b0eda132..25eb2f652 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/MergedPlaylistManager.java
@@ -39,7 +39,7 @@ public final class MergedPlaylistManager {
      * @param remotePlaylists remote playlists, already sorted by display index
      * @return merged playlists
      */
-    private static List<PlaylistLocalItem> merge(
+    public static List<PlaylistLocalItem> merge(
             final List<PlaylistMetadataEntry> localPlaylists,
             final List<PlaylistRemoteEntity> remotePlaylists) {
 
diff --git a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
index ab6315d91..847c54aa8 100644
--- a/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
+++ b/app/src/test/java/org/schabi/newpipe/database/playlist/PlaylistLocalItemTest.java
@@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
+import org.schabi.newpipe.local.bookmark.MergedPlaylistManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -15,7 +16,7 @@ public class PlaylistLocalItemTest {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
         final List<PlaylistLocalItem> mergedPlaylists =
-                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+                MergedPlaylistManager.merge(localPlaylists, remotePlaylists);
 
         assertEquals(0, mergedPlaylists.size());
     }
@@ -24,11 +25,11 @@ public class PlaylistLocalItemTest {
     public void onlyLocalPlaylists() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 0, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 3, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", false, -1, 0, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", false, -1, 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", false, -1, 3, 1));
         final List<PlaylistLocalItem> mergedPlaylists =
-                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+                MergedPlaylistManager.merge(localPlaylists, remotePlaylists);
 
         assertEquals(3, mergedPlaylists.size());
         assertEquals(0, mergedPlaylists.get(0).getDisplayIndex());
@@ -47,7 +48,7 @@ public class PlaylistLocalItemTest {
         remotePlaylists.add(new PlaylistRemoteEntity(
                 3, "name3", "url3", "", "", 4, 1L));
         final List<PlaylistLocalItem> mergedPlaylists =
-                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+                MergedPlaylistManager.merge(localPlaylists, remotePlaylists);
 
         assertEquals(3, mergedPlaylists.size());
         assertEquals(1, mergedPlaylists.get(0).getDisplayIndex());
@@ -59,14 +60,14 @@ public class PlaylistLocalItemTest {
     public void sameIndexWithDifferentName() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 0, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", false, -1, 0, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", false, -1, 1, 1));
         remotePlaylists.add(new PlaylistRemoteEntity(
                 1, "name3", "url1", "", "", 0, 1L));
         remotePlaylists.add(new PlaylistRemoteEntity(
                 2, "name4", "url2", "", "", 1, 1L));
         final List<PlaylistLocalItem> mergedPlaylists =
-                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+                MergedPlaylistManager.merge(localPlaylists, remotePlaylists);
 
         assertEquals(4, mergedPlaylists.size());
         assertTrue(mergedPlaylists.get(0) instanceof PlaylistMetadataEntry);
@@ -83,14 +84,14 @@ public class PlaylistLocalItemTest {
     public void sameNameWithDifferentIndex() {
         final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
         final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
-        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 1, 1));
-        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 3, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", false, -1, 1, 1));
+        localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", false, -1, 3, 1));
         remotePlaylists.add(new PlaylistRemoteEntity(
                 1, "name1", "url1", "", "", 0, 1L));
         remotePlaylists.add(new PlaylistRemoteEntity(
                 2, "name2", "url2", "", "", 2, 1L));
         final List<PlaylistLocalItem> mergedPlaylists =
-                PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
+                MergedPlaylistManager.merge(localPlaylists, remotePlaylists);
 
         assertEquals(4, mergedPlaylists.size());
         assertTrue(mergedPlaylists.get(0) instanceof PlaylistRemoteEntity);

From 3cc0205def80a6732ea374655a272ac7d3457503 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Mar 2024 14:14:31 +0100
Subject: [PATCH 124/141] Fix inconsistencies when removing playlist

Remove checkDisplayIndexModified because it was causing more problems than it solved. Now when adding new playlists they won't necessarily appear at the top, but will get sorted alphabetically along with the other playlists with index -1. This will be the case until any playlist is sorted, at which point all indices are assigned and newly added playlists will appear at the top again.
---
 .../local/bookmark/BookmarkFragment.java      | 31 +++----------------
 1 file changed, 5 insertions(+), 26 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 59e2582ff..922429382 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -56,7 +56,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
     private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
     @State
-    protected Parcelable itemsListState;
+    Parcelable itemsListState;
 
     private Subscription databaseSubscription;
     private CompositeDisposable disposables = new CompositeDisposable();
@@ -68,6 +68,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     private AtomicBoolean isLoadingComplete;
 
     /* Gives enough time to avoid interrupting user sorting operations */
+    @Nullable
     private DebounceSaver debounceSaver;
 
     private List<Pair<Long, LocalItem.LocalItemType>> deletedItems;
@@ -259,7 +260,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
             @Override
             public void onNext(final List<PlaylistLocalItem> subscriptions) {
                 if (debounceSaver == null || !debounceSaver.getIsModified()) {
-                    checkDisplayIndexModified(subscriptions);
                     handleResult(subscriptions);
                     isLoadingComplete.set(true);
                 }
@@ -349,30 +349,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                     LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM));
         }
 
-        debounceSaver.setHasChangesToSave();
-    }
-
-    private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
-        if (debounceSaver != null && debounceSaver.getIsModified()) {
-            return;
-        }
-
-        // Check if the display index does not match the actual index in the list.
-        // This may happen when a new list is created
-        // or on the first run after database migration
-        // or display index is not continuous for some reason
-        // or the user changes the display index.
-        boolean isDisplayIndexModified = false;
-        for (int i = 0; i < result.size(); i++) {
-            final PlaylistLocalItem item = result.get(i);
-            if (item.getDisplayIndex() != i) {
-                isDisplayIndexModified = true;
-                break;
-            }
-        }
-
-        if (debounceSaver != null && isDisplayIndexModified) {
+        if (debounceSaver != null) {
             debounceSaver.setHasChangesToSave();
+            saveImmediate();
         }
     }
 
@@ -482,7 +461,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
                 final int sourceIndex = source.getBindingAdapterPosition();
                 final int targetIndex = target.getBindingAdapterPosition();
                 final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
-                if (isSwapped) {
+                if (isSwapped && debounceSaver != null) {
                     debounceSaver.setHasChangesToSave();
                 }
                 return isSwapped;

From c9051d33c179c30d6c16d46999d79cd256a69fcf Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Mar 2024 14:36:31 +0100
Subject: [PATCH 125/141] Fix warnings and allow moving only up and down even
 in grid

---
 .../newpipe/local/bookmark/BookmarkFragment.java     | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
index 922429382..a366723e0 100644
--- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
@@ -1,7 +1,6 @@
 package org.schabi.newpipe.local.bookmark;
 
 import static org.schabi.newpipe.local.bookmark.MergedPlaylistManager.getMergedOrderedPlaylists;
-import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
 
 import android.content.DialogInterface;
 import android.os.Bundle;
@@ -244,7 +243,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     ///////////////////////////////////////////////////////////////////////////
 
     private Subscriber<List<PlaylistLocalItem>> getPlaylistsSubscriber() {
-        return new Subscriber<List<PlaylistLocalItem>>() {
+        return new Subscriber<>() {
             @Override
             public void onSubscribe(final Subscription s) {
                 showLoading();
@@ -276,7 +275,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
 
             @Override
             public void onComplete() {
-                // Do nothing.
             }
         };
     }
@@ -418,11 +416,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
     }
 
     private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
-        int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
-        if (shouldUseGridLayout(requireContext())) {
-            directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
-        }
-        return new ItemTouchHelper.SimpleCallback(directions,
+        // if adding grid layout, also include ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
+        // with an `if (shouldUseGridLayout()) ...`
+        return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                 ItemTouchHelper.ACTION_STATE_IDLE) {
             @Override
             public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,

From 38d48879014e987723e81dc12faf4397708f3cc8 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Mar 2024 14:46:13 +0100
Subject: [PATCH 126/141] Undo some unneeded changes to LocalPlaylistManager

---
 .../local/playlist/LocalPlaylistManager.java      | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
index 461ac2d0a..dd9307675 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistManager.java
@@ -133,18 +133,13 @@ public class LocalPlaylistManager {
     }
 
     public Maybe<Integer> renamePlaylist(final long playlistId, final String name) {
-        return modifyPlaylist(playlistId, name, THUMBNAIL_ID_LEAVE_UNCHANGED, false, -1);
+        return modifyPlaylist(playlistId, name, THUMBNAIL_ID_LEAVE_UNCHANGED, false);
     }
 
     public Maybe<Integer> changePlaylistThumbnail(final long playlistId,
                                                   final long thumbnailStreamId,
                                                   final boolean isPermanent) {
-        return modifyPlaylist(playlistId, null, thumbnailStreamId, isPermanent, -1);
-    }
-
-    public Maybe<Integer> updatePlaylistDisplayIndex(final long playlistId,
-                                                     final long displayIndex) {
-        return modifyPlaylist(playlistId, null, THUMBNAIL_ID_LEAVE_UNCHANGED, false, displayIndex);
+        return modifyPlaylist(playlistId, null, thumbnailStreamId, isPermanent);
     }
 
     public long getPlaylistThumbnailStreamId(final long playlistId) {
@@ -168,8 +163,7 @@ public class LocalPlaylistManager {
     private Maybe<Integer> modifyPlaylist(final long playlistId,
                                           @Nullable final String name,
                                           final long thumbnailStreamId,
-                                          final boolean isPermanent,
-                                          final long displayIndex) {
+                                          final boolean isPermanent) {
         return playlistTable.getPlaylist(playlistId)
                 .firstElement()
                 .filter(playlistEntities -> !playlistEntities.isEmpty())
@@ -182,9 +176,6 @@ public class LocalPlaylistManager {
                         playlist.setThumbnailStreamId(thumbnailStreamId);
                         playlist.setIsThumbnailPermanent(isPermanent);
                     }
-                    if (displayIndex != -1) {
-                        playlist.setDisplayIndex(displayIndex);
-                    }
                     return playlistTable.update(playlist);
                 }).subscribeOn(Schedulers.io());
     }

From 5e7ad6ffd160633f15d5ad02f651ee3207539224 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 30 Mar 2024 15:14:18 +0100
Subject: [PATCH 127/141] Fix fragments without empty constructor

---
 .../schabi/newpipe/fragments/detail/DescriptionFragment.java   | 3 +++
 .../newpipe/fragments/list/channel/ChannelAboutFragment.java   | 2 ++
 2 files changed, 5 insertions(+)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
index 7fc66b42f..6a7c5ca73 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
@@ -30,6 +30,9 @@ public class DescriptionFragment extends BaseDescriptionFragment {
         this.streamInfo = streamInfo;
     }
 
+    public DescriptionFragment() {
+    }
+
 
     @Nullable
     @Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
index 674a13c5b..a67d37dc1 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
@@ -30,6 +30,8 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
         this.channelInfo = channelInfo;
     }
 
+    public ChannelAboutFragment() {
+    }
 
     @Override
     protected void initViews(final View rootView, final Bundle savedInstanceState) {

From e42c9abdde72e8af8fcbdaef5a46ce329a5152d4 Mon Sep 17 00:00:00 2001
From: pratyaksh1610 <pratyakshkhuranaofficial@gmail.com>
Date: Tue, 20 Dec 2022 01:44:42 +0530
Subject: [PATCH 128/141] moved player notification to notification section

---
 app/src/main/res/xml/appearance_settings.xml    | 8 --------
 app/src/main/res/xml/notifications_settings.xml | 8 ++++++++
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml
index beb46cdf5..d8f6bf552 100644
--- a/app/src/main/res/xml/appearance_settings.xml
+++ b/app/src/main/res/xml/appearance_settings.xml
@@ -23,14 +23,6 @@
         app:singleLineTitle="false"
         app:iconSpaceReserved="false" />
 
-    <PreferenceScreen
-        android:key="@string/player_notification_screen_key"
-        android:fragment="org.schabi.newpipe.settings.PlayerNotificationSettingsFragment"
-        android:summary="@string/settings_category_player_notification_summary"
-        android:title="@string/settings_category_player_notification_title"
-        app:singleLineTitle="false"
-        app:iconSpaceReserved="false" />
-
     <SwitchPreferenceCompat
         android:defaultValue="true"
         android:key="@string/show_hold_to_append_key"
diff --git a/app/src/main/res/xml/notifications_settings.xml b/app/src/main/res/xml/notifications_settings.xml
index 5e2858c9e..8faf8fa54 100644
--- a/app/src/main/res/xml/notifications_settings.xml
+++ b/app/src/main/res/xml/notifications_settings.xml
@@ -4,6 +4,14 @@
 	android:key="general_preferences"
 	android:title="@string/notifications">
 
+	<PreferenceScreen
+		android:key="@string/player_notification_screen_key"
+		android:fragment="org.schabi.newpipe.settings.PlayerNotificationSettingsFragment"
+		android:summary="@string/settings_category_player_notification_summary"
+		android:title="@string/settings_category_player_notification_title"
+		app:singleLineTitle="false"
+		app:iconSpaceReserved="false" />
+
 	<SwitchPreference
 		android:defaultValue="false"
 		android:key="@string/enable_streams_notifications"

From 974167fcb80a3d582da03652767847784dd3b82e Mon Sep 17 00:00:00 2001
From: TobiGr <tobigr@users.noreply.github.com>
Date: Sat, 30 Mar 2024 16:17:31 +0100
Subject: [PATCH 129/141] Add comment that empty constructors are needed for
 IcePick

See 5e7ad6ffd160633f15d5ad02f651ee3207539224 and https://github.com/TeamNewPipe/NewPipe/pull/10781#discussion_r1545351144
---
 .../org/schabi/newpipe/fragments/detail/DescriptionFragment.java | 1 +
 .../newpipe/fragments/list/channel/ChannelAboutFragment.java     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
index 6a7c5ca73..581e54156 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java
@@ -31,6 +31,7 @@ public class DescriptionFragment extends BaseDescriptionFragment {
     }
 
     public DescriptionFragment() {
+        // keep empty constructor for IcePick when resuming fragment from memory
     }
 
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
index a67d37dc1..0dc2fb65a 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelAboutFragment.java
@@ -31,6 +31,7 @@ public class ChannelAboutFragment extends BaseDescriptionFragment {
     }
 
     public ChannelAboutFragment() {
+        // keep empty constructor for IcePick when resuming fragment from memory
     }
 
     @Override

From 0b28c688c69a1ccc264c91fc0bc8cd5e6baf46e1 Mon Sep 17 00:00:00 2001
From: Hosted Weblate <hosted@weblate.org>
Date: Mon, 1 Apr 2024 13:38:40 +0200
Subject: [PATCH 130/141] Translated using Weblate (Estonian)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 46.7% (36 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (German)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Korean)

Currently translated at 31.1% (24 of 77 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Korean)

Currently translated at 98.9% (726 of 734 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (German)

Currently translated at 99.7% (732 of 734 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (German)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Hungarian)

Currently translated at 16.8% (13 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (730 of 730 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Apious <apious@kakao.com>
Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Pi-Cla <pirateclip@protonmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Tim Trek <T.Trek@byom.de>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Tấn Lực Trương <september122022ios16@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
---
 app/src/main/res/values-ar/strings.xml        |  11 ++
 app/src/main/res/values-be/strings.xml        |   1 +
 app/src/main/res/values-cs/strings.xml        |  11 ++
 app/src/main/res/values-de/strings.xml        |   6 +
 app/src/main/res/values-es/strings.xml        |  11 ++
 app/src/main/res/values-et/strings.xml        |  11 ++
 app/src/main/res/values-hu/strings.xml        |  11 ++
 app/src/main/res/values-in/strings.xml        |  11 ++
 app/src/main/res/values-it/strings.xml        |   7 +
 app/src/main/res/values-ja/strings.xml        |   1 +
 app/src/main/res/values-ko/strings.xml        |   5 +-
 app/src/main/res/values-nl/strings.xml        |  11 ++
 app/src/main/res/values-or/strings.xml        |  11 ++
 app/src/main/res/values-pl/strings.xml        |  11 ++
 app/src/main/res/values-ro/strings.xml        |  19 +-
 app/src/main/res/values-ru/strings.xml        |   1 +
 app/src/main/res/values-sk/strings.xml        |  13 +-
 app/src/main/res/values-sr/strings.xml        |   7 +
 app/src/main/res/values-sv/strings.xml        |   1 +
 app/src/main/res/values-tr/strings.xml        |  15 +-
 app/src/main/res/values-uk/strings.xml        |   3 +-
 app/src/main/res/values-vi/strings.xml        | 177 ++++++++++--------
 app/src/main/res/values-zh-rCN/strings.xml    |  11 ++
 app/src/main/res/values-zh-rHK/strings.xml    |   1 +
 app/src/main/res/values-zh-rTW/strings.xml    |  11 ++
 .../metadata/android/hu/short_description.txt |   2 +-
 .../metadata/android/ko/changelogs/65.txt     |  27 +++
 .../metadata/android/ko/changelogs/66.txt     |  29 +++
 .../metadata/android/ko/changelogs/68.txt     |  31 +++
 .../metadata/android/ko/changelogs/69.txt     |  19 ++
 .../metadata/android/ko/changelogs/70.txt     |  25 +++
 .../metadata/android/ko/changelogs/71.txt     |  10 +
 .../metadata/android/ko/changelogs/730.txt    |   2 +
 .../metadata/android/ko/changelogs/740.txt    |  23 +++
 .../metadata/android/ko/changelogs/750.txt    |  22 +++
 .../metadata/android/ko/changelogs/760.txt    |  43 +++++
 .../metadata/android/ko/changelogs/770.txt    |   4 +
 .../metadata/android/ko/changelogs/780.txt    |  12 ++
 .../metadata/android/ko/changelogs/790.txt    |  14 ++
 .../metadata/android/ko/changelogs/800.txt    |  27 +++
 .../metadata/android/ko/changelogs/810.txt    |  19 ++
 .../metadata/android/vi/changelogs/65.txt     |  32 +++-
 .../metadata/android/vi/changelogs/66.txt     |  33 ++++
 .../metadata/android/vi/changelogs/68.txt     |  31 +++
 .../metadata/android/vi/changelogs/69.txt     |  19 ++
 .../metadata/android/vi/changelogs/820.txt    |   2 +-
 46 files changed, 697 insertions(+), 107 deletions(-)
 create mode 100644 fastlane/metadata/android/ko/changelogs/65.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/66.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/68.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/69.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/70.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/71.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/730.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/740.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/750.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/760.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/770.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/780.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/790.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/800.txt
 create mode 100644 fastlane/metadata/android/ko/changelogs/810.txt
 create mode 100644 fastlane/metadata/android/vi/changelogs/66.txt
 create mode 100644 fastlane/metadata/android/vi/changelogs/68.txt
 create mode 100644 fastlane/metadata/android/vi/changelogs/69.txt

diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index eefe24390..82173d758 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -869,4 +869,15 @@
     <string name="show_more">عرض المزيد</string>
     <string name="show_less">عرض أقل</string>
     <string name="notification_actions_summary_android13">قم بتحرير كل إجراء إعلام أدناه من خلال النقر عليه. يتم تعيين الإجراءات الثلاثة الأولى (تشغيل/إيقاف مؤقت، السابق والتالي) بواسطة النظام ولا يمكن تخصيصها.</string>
+    <string name="error_insufficient_storage">لا توجد مساحة خالية كافية على الجهاز</string>
+    <string name="reset_settings_title">اعادة ضبط الإعداداتِ</string>
+    <string name="settings_category_backup_restore_title">النسخ الاحتياطيُّ والاستعادة</string>
+    <string name="reset_settings_summary">أعيدوا جميع الإعدادات إلى قيمهم الافتراضية</string>
+    <string name="reset_all_settings">ستؤدي إعادة ضبط جميع الإعدادات إلى تجاهل جميع إعداداتك المفضلة وإعادة تشغيل التطبيق.
+\n
+\nهل انت متأكد انك تريد المتابعة؟</string>
+    <string name="yes">نعم</string>
+    <string name="auto_update_check_description">يمكن لـ NewPipe البحث تلقائيًا عن الإصدارات الجديدة من وقت لآخر وإعلامك بمجرد توفرها.
+\nهل تريد تمكين هذا؟</string>
+    <string name="no">لا</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml
index 5990738cf..dceaaf6c5 100644
--- a/app/src/main/res/values-be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -839,4 +839,5 @@
     <string name="show_more">Паказаць больш</string>
     <string name="show_less">Паказаць менш</string>
     <string name="notification_actions_summary_android13">Адрэдагуйце кожнае дзеянне апавяшчэння, націснуўшы на яго. Першыя тры дзеянні (прайграванне/паўза, папярэдняе і наступнае) задаюцца сістэмай і не могуць быць зменены.</string>
+    <string name="error_insufficient_storage">Недастаткова вольнага месца на прыладзе</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 0a4751261..34537446c 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -827,4 +827,15 @@
     <string name="show_more">Zobrazit více</string>
     <string name="notification_actions_summary_android13">Upravte každou akci oznámení níže poklepáním. První tři akce (přehrání/pozastavení, předchozí a další) jsou nastaveny systémem a nemohou být přizpůsobeny.</string>
     <string name="show_less">Zobrazit méně</string>
+    <string name="error_insufficient_storage">Nedostatek volného místa v zařízení</string>
+    <string name="settings_category_backup_restore_title">Záloha a obnovení</string>
+    <string name="reset_settings_title">Obnovit nastavení</string>
+    <string name="reset_settings_summary">Obnovení všech nastavení na výchozí hodnoty</string>
+    <string name="reset_all_settings">Obnovením nastavení se zruší všechna preferovaná nastavení a aplikace se restartuje.
+\n
+\nJste si jisti, že chcete pokračovat?</string>
+    <string name="yes">Ano</string>
+    <string name="no">Ne</string>
+    <string name="auto_update_check_description">NewPipe může čas od času automaticky kontrolovat nové verze a upozornit vás na jejich dostupnost.
+\nChcete tuto funkci povolit?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index e94c8f757..3a308fb25 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -813,4 +813,10 @@
     <string name="show_more">Mehr zeigen</string>
     <string name="show_less">Weniger zeigen</string>
     <string name="notification_actions_summary_android13">Bearbeite jede Benachrichtigungsaktion unten, indem du auf sie tippst. Die ersten drei Aktionen (Abspielen/Pause, Zurück und Weiter) sind vom System vorgegeben und können nicht angepasst werden.</string>
+    <string name="error_insufficient_storage">Nicht genug freier Speicher auf dem Gerät</string>
+    <string name="reset_settings_title">Einstellungen zurücksetzen</string>
+    <string name="reset_settings_summary">Setzt alle Einstellungen auf ihre Standardwerte zurück</string>
+    <string name="yes">Ja</string>
+    <string name="no">Nein</string>
+    <string name="settings_category_backup_restore_title">Sichern und Wiederherstellen</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 3a8681e44..ce0b32603 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -830,4 +830,15 @@
     <string name="show_more">Ver más</string>
     <string name="show_less">Mostrar menos</string>
     <string name="notification_actions_summary_android13">Edite cada acción de notificación pulsando sobre ella. Las tres primeras acciones (reproducir/pausa, anterior y siguiente) las establece el sistema y no se pueden personalizar.</string>
+    <string name="error_insufficient_storage">No hay suficiente espacio libre en el dispositivo</string>
+    <string name="settings_category_backup_restore_title">Copia de seguridad y restaurar</string>
+    <string name="reset_settings_title">Reiniciar ajustes</string>
+    <string name="reset_settings_summary">Restablecer todos los ajustes a sus valores predeterminados</string>
+    <string name="reset_all_settings">Restablecer todos los ajustes descartará todos sus ajustes preferidos y reiniciará la aplicación.
+\n
+\n¿Estas seguro que deseas continuar?</string>
+    <string name="yes">Sí</string>
+    <string name="no">No</string>
+    <string name="auto_update_check_description">NewPipe puede buscar automáticamente nuevas versiones de vez en cuando y notificarle cuando estén disponibles.
+\n¿Quieres habilitar esto?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 5864d5519..1d3fcdcb8 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -813,4 +813,15 @@
     </plurals>
     <string name="show_less">Näita vähem</string>
     <string name="notification_actions_summary_android13">Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta.</string>
+    <string name="settings_category_backup_restore_title">Varundus ja taastamine</string>
+    <string name="auto_update_check_description">NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada.
+\nKas sa soovid sellist võimalust kasuutada?</string>
+    <string name="reset_settings_title">Lähtesta seadistused</string>
+    <string name="reset_settings_summary">Lähtesta kõik seadistused nende vaikimisi väärtusteks</string>
+    <string name="error_insufficient_storage">Seadmes pole enam piisavalt vaba ruumi</string>
+    <string name="reset_all_settings">Kui lähtestad kõik seadistused, siis kõik sinu muudetud seadistused asendatakse vaikimisi väärtustega ja rakendus käivitub uuesti.
+\n
+\nKas sa soovid jätkata?</string>
+    <string name="yes">Jah</string>
+    <string name="no">Ei</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 5ca829ed8..5a402c94c 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -813,4 +813,15 @@
     <string name="channel_tab_tracks">Dalok</string>
     <string name="channel_tab_shorts">Rövidek</string>
     <string name="channel_tab_livestreams">Élő</string>
+    <string name="error_insufficient_storage">Nincs elég szabad hely az eszközön</string>
+    <string name="yes">Igen</string>
+    <string name="no">Nem</string>
+    <string name="settings_category_backup_restore_title">Biztonsági mentés és visszaállítás</string>
+    <string name="auto_update_check_description">A NewPipe időről időre automatikusan ellenőrzi az új verziókat, és értesít, amint azok elérhetővé válnak.
+\nSzeretné engedélyezni ezt?</string>
+    <string name="reset_settings_title">Beállítások alaphelyzetbe állítása</string>
+    <string name="reset_settings_summary">Minden beállítás visszaállítása alapértelmezett értékre</string>
+    <string name="reset_all_settings">Az összes beállítás visszaállítása elveti az összes preferált beállítást, és újraindítja az alkalmazást.
+\n
+\nBiztosan folytatja?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index d715f2232..71900400e 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -799,4 +799,15 @@
     <plurals name="replies">
         <item quantity="other">%s balasan</item>
     </plurals>
+    <string name="error_insufficient_storage">Tidak ada ruang yang cukup pada perangkat</string>
+    <string name="yes">Ya</string>
+    <string name="no">Tidak</string>
+    <string name="reset_settings_title">Atur ulang pengaturan</string>
+    <string name="reset_settings_summary">Atur ulang pengaturan ke nilai bawaan</string>
+    <string name="reset_all_settings">Mengatur ulang semua pengaturan akan mengabaikan pengaturan Anda yang disukai dan memulai ulang aplikasi.
+\n
+\nApakah Anda yakin ingin melanjutkan?</string>
+    <string name="auto_update_check_description">NewPipe dapat memeriksa versi baru secara berkala dan memberi tahu Anda ketika ada yang baru.
+\nApakah Anda ingin mengaktifkan ini?</string>
+    <string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 5ddb6c672..35a722a57 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -827,4 +827,11 @@
     <string name="show_more">Mostra altro</string>
     <string name="notification_actions_summary_android13">Le azioni dei pulsanti della notifica possono essere modificate qua sotto. Le prime tre (riproduci/pausa, precedente e successivo) sono impostate dal sistema e non possono essere cambiate.</string>
     <string name="show_less">Mostra meno</string>
+    <string name="error_insufficient_storage">Non abbastanza spazio libero sul dispositivo</string>
+    <string name="settings_category_backup_restore_title">Backup e ripristino</string>
+    <string name="reset_settings_title">Azzera le impostazioni</string>
+    <string name="reset_all_settings">L\'azzeramento di tutte le impostazioni eliminerà tutte le proprie impostazioni e riavvierà l\'app.
+\n
+\nSei sicuro di voler procedere?</string>
+    <string name="reset_settings_summary">Azzera tutte le impostazioni ai loro valori predefiniti</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index afcf34429..6cbc3c084 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -799,4 +799,5 @@
     <string name="show_more">もっと見る</string>
     <string name="show_less">表示を少なくする</string>
     <string name="notification_actions_summary_android13">以下の通知アクションをタップして編集します。 最初の3つのアクション (再生/一時停止、前へ、次へ)はシステムによって設定されており、カスタマイズすることはできません。</string>
+    <string name="error_insufficient_storage">デバイスの空き容量が不足しています</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index d17e7599d..939d97441 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -505,8 +505,8 @@
     <string name="show_description_title">설명 표시하기</string>
     <string name="clear_queue_confirmation_summary">한 플레이어에서 다른 플레이어로 전환하면 대기열이 대체될 수 있습니다</string>
     <string name="night_theme_title">어두운 테마</string>
-    <string name="notification_actions_at_most_three">최대 3개까지 축소 알림에 표시될 항목을 고를 수 있습니다!</string>
-    <string name="notification_actions_summary">아래의 각 알림 작업을 눌러 편집하세요. 오른쪽에 있는 확인란을 사용하여 압축 알림에 표시할 항목을 최대 3개까지 선택</string>
+    <string name="notification_actions_at_most_three">축소된 알림에서 최대 3개까지 표시될 항목을 고를 수 있습니다!</string>
+    <string name="notification_actions_summary">아래의 각 알림 작업을 눌러 편집하세요 오른쪽에 있는 확인란을 사용하여 압축 알림에 표시할 항목을 최대 3개까지 선택</string>
     <string name="youtube_restricted_mode_enabled_title">YouTube의 \'제한 모드\' 켜기</string>
     <string name="hash_channel_description">비디오 해싱 진행 알림</string>
     <string name="streams_notification_channel_name">새로운 스트림</string>
@@ -793,4 +793,5 @@
     <string name="channel_tab_channels">채널</string>
     <string name="previous_stream">이전 스트림</string>
     <string name="channel_tab_livestreams">실시간</string>
+    <string name="notification_actions_summary_android13">아래의 각 알림 작업을 탭하여 편집하세요. 처음 세 가지 작업(재생/일시 중지, 이전 및 다음)은 시스템에 의해 설정되며 사용자 정의할 수 없습니다.</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 9b690f770..2a5206699 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -813,4 +813,15 @@
     <string name="show_more">Meer tonen</string>
     <string name="show_less">Minder tonen</string>
     <string name="notification_actions_summary_android13">Bewerk elke meldings­actie hieronder door erop te tikken. De eerste drie acties (afspelen/pauzeren, vorige en volgende) zijn ingesteld door het systeem en kunnen niet worden aangepast.</string>
+    <string name="error_insufficient_storage">Onvoldoende vrije ruimte op het apparaat</string>
+    <string name="yes">Ja</string>
+    <string name="reset_settings_summary">Herstelt alle instellingen naar hun standaard­waarde</string>
+    <string name="reset_all_settings">Als u alle instellingen reset worden al uw voorkeurs­instellingen verwijderd en wordt de app opnieuw gestart.
+\n
+\nWeet u zeker dat u verder wilt gaan?</string>
+    <string name="no">Nee</string>
+    <string name="settings_category_backup_restore_title">Back-up en herstel</string>
+    <string name="auto_update_check_description">NewPipe kan van tijd tot tijd auto­matisch controleren op nieuwe versies en u op de hoogte stellen zodra deze beschik­baar zijn.
+\nWilt u dit inschakelen?</string>
+    <string name="reset_settings_title">Instellingen resetten</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index e6daac644..f9faa8324 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -813,4 +813,15 @@
         <item quantity="other">%s ଉତ୍ତରଗୁଡ଼ିକ</item>
     </plurals>
     <string name="show_less">କମ୍ ଦର୍ଶାନ୍ତୁ</string>
+    <string name="error_insufficient_storage">ଉପକରଣରେ ପର୍ଯ୍ୟାପ୍ତ ଖାଲି ସ୍ଥାନ ନାହିଁ</string>
+    <string name="settings_category_backup_restore_title">ବ୍ୟାକଅପ୍ ଏବଂ ପୁନରୁଦ୍ଧାର କରନ୍ତୁ</string>
+    <string name="yes">ହଁ</string>
+    <string name="no">ନା</string>
+    <string name="auto_update_check_description">NewPipe ସ୍ୱୟଂଚାଳିତ ଭାବରେ ସମୟ ସମୟରେ ନୂତନ ସଂସ୍କରଣଗୁଡିକ ଯାଞ୍ଚ କରିପାରିବ ଏବଂ ଥରେ ଉପଲବ୍ଧ ହେବା ପରେ ଆପଣଙ୍କୁ ସୂଚିତ କରିପାରିବ ।
+\nଆପଣ ଏହାକୁ ସକ୍ଷମ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?</string>
+    <string name="reset_settings_title">ସେଟିଂସମୂହ ପୁନଃସେଟ୍ କରନ୍ତୁ</string>
+    <string name="reset_settings_summary">ସମସ୍ତ ସେଟିଂସମୂହକୁ ସେମାନଙ୍କର ଡିଫଲ୍ଟ ମୂଲ୍ୟରେ ପୁନଃସେଟ୍ କରନ୍ତୁ</string>
+    <string name="reset_all_settings">ସମସ୍ତ ସେଟିଂସମୂହକୁ ପୁନଃ ସେଟ୍ କରିବା ଦ୍ବାରା ଆପଣଙ୍କର ସମସ୍ତ ପସନ୍ଦିତ ସେଟିଂସମୂହ ପରିତ୍ୟାଗ ହେବ ଏବଂ ଆପ୍ ପୁନଃ ସେଟ୍ଆରମ୍ଭ ହେବ ।
+\n
+\nଆପଣ ଆଗକୁ ବଢିବାକୁ ଚାହୁଁଛନ୍ତି କି?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 6fd61b80e..01f242cd9 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -836,4 +836,15 @@
     <string name="show_more">Pokaż więcej</string>
     <string name="show_less">Pokaż mniej</string>
     <string name="notification_actions_summary_android13">Edytuj każdą poniższą akcję powiadomienia, naciskając ją. Pierwsze trzy akcje (odtwórz/wstrzymaj, poprzedni i następny) są ustawione przez system i nie można ich dostosować</string>
+    <string name="error_insufficient_storage">Za mało wolnego miejsca na urządzeniu</string>
+    <string name="settings_category_backup_restore_title">Kopia zapasowa i przywracanie</string>
+    <string name="reset_settings_summary">Resetuje wszystkie ustawienia do ich domyślnych wartości</string>
+    <string name="reset_settings_title">Resetuj ustawienia</string>
+    <string name="reset_all_settings">Zresetowanie wszystkich ustawień odrzuci wszystkie Twoje preferowane ustawienia i ponownie uruchomi aplikację.
+\n
+\nCzy na pewno chcesz kontynuować?</string>
+    <string name="yes">Tak</string>
+    <string name="no">Nie</string>
+    <string name="auto_update_check_description">NewPipe może od czasu do czasu automatycznie sprawdzać dostępność nowych wersji i powiadamiać Cię, gdy tylko będą dostępne.
+\nCzy chcesz to włączyć?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index ccc25dc4b..c9eaaf2c9 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -30,7 +30,7 @@
     <string name="dark_theme_title">Întunecat</string>
     <string name="light_theme_title">Luminos</string>
     <string name="download_dialog_title">Descărcați</string>
-    <string name="show_next_and_similar_title">Arată videoclipurile care \'Urmează\' şi cele \'Similare\'</string>
+    <string name="show_next_and_similar_title">Arată videoclipurile care \'Urmează\' și cele \'Similare\'</string>
     <string name="unsupported_url">URL nesuportat</string>
     <string name="content_language_title">Limba dorită a conținutului</string>
     <string name="settings_category_video_audio_title">Video și Audio</string>
@@ -154,7 +154,7 @@
     <string name="copyright" formatted="true">© %1$s de %2$s sub %3$s</string>
     <string name="tab_about">Despre &amp; FAQ</string>
     <string name="tab_licenses">Licențe</string>
-    <string name="app_description">Un player de streaming „uşor” liber, pentru Android.</string>
+    <string name="app_description">Un player de streaming „ușor” liber, pentru Android.</string>
     <string name="view_on_github">Vedeți pe GitHub</string>
     <string name="app_license_title">Licența NewPipe</string>
     <string name="contribution_encouragement">Fie că aveți idei de: traducere, modificări de design, curățare a codului sau modificări de cod cu adevărat importante - ajutorul este întotdeauna binevenit. Cu cât se face mai mult, cu atât mai bine devine!</string>
@@ -227,23 +227,23 @@
     <string name="play_queue_remove">Eliminați</string>
     <string name="play_queue_stream_detail">Detalii</string>
     <string name="play_queue_audio_settings">Setări Audio</string>
-    <string name="hold_to_append">Apăsaţi pentru a adăuga în coadă</string>
+    <string name="hold_to_append">Apăsați pentru a adăuga în coadă</string>
     <string name="start_here_on_background">Începeți redarea în fundal</string>
     <string name="start_here_on_popup">Începeți redarea în popup</string>
-    <string name="drawer_open">Deschdeţi sertarul</string>
-    <string name="drawer_close">Închideţi sertarul</string>
-    <string name="preferred_open_action_settings_title">Opţiunea de deschidere preferată</string>
+    <string name="drawer_open">Deschdeți sertarul</string>
+    <string name="drawer_close">Închideți sertarul</string>
+    <string name="preferred_open_action_settings_title">Opțiunea de deschidere preferată</string>
     <string name="preferred_open_action_settings_summary">Acțiune implicită la deschiderea conținutului - %s</string>
     <string name="video_player">Player Video</string>
     <string name="background_player">Player Fundal</string>
     <string name="popup_player">Player Popup</string>
     <string name="always_ask_open_action">Întrebați întotdeauna</string>
     <string name="no_streams_available_download">Nu există fluxuri disponibile pentru descărcare</string>
-    <string name="detail_drag_description">Trageţi pentru a reordona</string>
+    <string name="detail_drag_description">Trageți pentru a reordona</string>
     <string name="create">Creați</string>
     <string name="dismiss">Respingeți</string>
-    <string name="rename">Redenumiţi</string>
-    <string name="donation_title">Donaţi</string>
+    <string name="rename">Redenumiți</string>
+    <string name="donation_title">Donați</string>
     <string name="import_settings">De asemenea, doriți să importați setări?</string>
     <string name="name">Nume</string>
     <string name="playlist_creation_success">Listă de redare creată</string>
@@ -828,4 +828,5 @@
     <string name="show_more">Arată mai multe</string>
     <string name="show_less">Arată mai puține</string>
     <string name="channel_tab_tracks">Piste</string>
+    <string name="error_insufficient_storage">Nu este suficient spațiu liber pe dispozitiv</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index a885631eb..df566853b 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -832,4 +832,5 @@
         <item quantity="other">%s ответов</item>
     </plurals>
     <string name="notification_actions_summary_android13">Отредактируйте каждое действие уведомления ниже, нажав на него. Первые три действия (воспроизведение/пауза, предыдущее и следующее) задаются системой и не подлежат настройке.</string>
+    <string name="error_insufficient_storage">Недостаточно свободного места на устройстве</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 443809526..e3aa5b6bc 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">súbor nemožno prepísať</string>
     <string name="download_already_pending">Súbor s rovnakým názvom už čaká na stiahnutie</string>
     <string name="error_postprocessing_stopped">NewPipe bol ukončený počas spracovávania súboru</string>
-    <string name="error_insufficient_storage_left">Máš plnú pamäť</string>
+    <string name="error_insufficient_storage_left">V zariadení už nie je voľné miesto</string>
     <string name="error_progress_lost">Nemožno pokračovať, súbor bol vymazaný</string>
     <string name="error_timeout">Spojenie vypršalo</string>
     <string name="confirm_prompt">Chcete vymazať históriu sťahovania alebo odstrániť všetky stiahnuté súbory\?</string>
@@ -827,4 +827,15 @@
     <string name="video_details_list_item">- %1$s: %2$s</string>
     <string name="share_playlist_content_details">%1$s
 \n%2$s</string>
+    <string name="error_insufficient_storage">Nedostatok voľného miesta v zariadení</string>
+    <string name="reset_settings_summary">Obnoví všetky nastavenia na pôvodné hodnoty</string>
+    <string name="reset_settings_title">Obnoviť nastavenia</string>
+    <string name="settings_category_backup_restore_title">Záloha a obnovenie</string>
+    <string name="reset_all_settings">Obnovením nastavení sa zrušia všetky preferované nastavenia a aplikácia sa reštartuje.
+\n
+\nSte si istí, že chcete pokračovať?</string>
+    <string name="no">Nie</string>
+    <string name="yes">Áno</string>
+    <string name="auto_update_check_description">NewPipe môže z času na čas automaticky kontrolovať nové verzie a upozorniť vás, keď budú k dispozícii.
+\nChcete to povoliť?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 596fbe263..996b3fa2e 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -827,4 +827,11 @@
     <string name="show_more">Прикажи више</string>
     <string name="show_less">Прикажи мање</string>
     <string name="notification_actions_summary_android13">Измените сваку радњу обавештења у наставку тако што ћете је додирнути. Прве три радње (пусти/паузирај, претходни и следећи) поставља систем и не могу се прилагодити.</string>
+    <string name="error_insufficient_storage">Нема довољно слободног меморијског простора на уређају</string>
+    <string name="settings_category_backup_restore_title">Прављење резервне копије и враћање</string>
+    <string name="reset_settings_title">Ресетуј подешавања</string>
+    <string name="reset_settings_summary">Ресетујте сва подешавања на подразумеване вредности</string>
+    <string name="reset_all_settings">Ресетовање свих подешавања ће одбацити сва жељена подешавања и поново покренути апликацију.
+\n
+\nЖелите ли заиста да наставите?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index a5c9aaf2b..54aab0cdf 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -813,4 +813,5 @@
     </plurals>
     <string name="show_less">Visa mindre</string>
     <string name="notification_actions_summary_android13">Redigera varje aviseringsåtgärd nedan genom att trycka på den. De tre första åtgärderna (spela/pausa, föregående och nästa) är satta av systemet och kan inte ändras.</string>
+    <string name="error_insufficient_storage">Inte tillräckligt med ledigt utrymme på enheten</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index fd12b6a35..adecbf3ff 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -474,8 +474,8 @@
     <string name="feed_group_dialog_select_subscriptions">Abonelikleri seç</string>
     <string name="feed_group_dialog_empty_selection">Abonelik seçilmedi</string>
     <plurals name="feed_group_dialog_selection_count">
-        <item quantity="one">%d öğe seçildi</item>
-        <item quantity="other">%d öğe seçildi</item>
+        <item quantity="one">%d öge seçildi</item>
+        <item quantity="other">%d öge seçildi</item>
     </plurals>
     <string name="feed_group_dialog_empty_name">Boş grup adı</string>
     <string name="feed_group_dialog_delete_message">Bu grubu silmek istiyor musunuz\?</string>
@@ -813,4 +813,15 @@
         <item quantity="one">%s yanıt</item>
         <item quantity="other">%s yanıt</item>
     </plurals>
+    <string name="error_insufficient_storage">Aygıtta yeterli boş alan yok</string>
+    <string name="settings_category_backup_restore_title">Yedekle ve geri yükle</string>
+    <string name="reset_settings_title">Ayarları sıfırla</string>
+    <string name="reset_settings_summary">Tüm ayarları öntanımlı değerlerine sıfırlayın</string>
+    <string name="reset_all_settings">Tüm ayarları sıfırlamak, tercih edilen tüm ayarlarınızı kaldırır ve uygulamayı yeniden başlatır.
+\n
+\nDevam etmek istediğinizden emin misiniz?</string>
+    <string name="yes">Evet</string>
+    <string name="no">Hayır</string>
+    <string name="auto_update_check_description">NewPipe zaman zaman yeni sürümleri kendiliğinden denetleyebilir ve kullanılabilir olduklarında sizi bilgilendirebilir.
+\nBunu etkinleştirmek istiyor musunuz?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 1227d7db4..fa63499ea 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -398,7 +398,7 @@
     <string name="overwrite_failed">не можу перезаписати файл</string>
     <string name="download_already_pending">Завантаження з такою назвою вже додано в чергу</string>
     <string name="error_postprocessing_stopped">NewPipe був закритий під час роботи над файлом</string>
-    <string name="error_insufficient_storage_left">На пристрої не залишилося вільного місця</string>
+    <string name="error_insufficient_storage_left">На пристрої не залишилося вільного простору</string>
     <string name="error_progress_lost">Прогрес втрачено через видалення файлу</string>
     <string name="error_timeout">Час очікування з\'єднання вичерпано</string>
     <string name="confirm_prompt">Очистити історію завантажень чи завантажені файли\?</string>
@@ -832,4 +832,5 @@
         <item quantity="other">%s відповідей</item>
     </plurals>
     <string name="show_less">Показати менше</string>
+    <string name="error_insufficient_storage">Недостатньо вільного простору на пристрої</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 393d3cdba..dcb8d2f84 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -10,30 +10,30 @@
     <string name="share">Chia sẻ</string>
     <string name="download">Tải về</string>
     <string name="search">Tìm kiếm</string>
-    <string name="settings">Thiết đặt</string>
+    <string name="settings">Cài đặt</string>
     <string name="did_you_mean">Ý bạn là \"%1$s\"\?</string>
     <string name="share_dialog_title">Chia sẻ với</string>
-    <string name="use_external_video_player_title">Sử dụng trình phát băng hình bên ngoài</string>
+    <string name="use_external_video_player_title">Sử dụng trình phát video bên ngoài</string>
     <string name="use_external_video_player_summary">Loại bỏ âm thanh ở một số độ phân giải</string>
     <string name="use_external_audio_player_title">Sử dụng trình phát âm thanh bên ngoài</string>
     <string name="controls_popup_title">Trình phát nổi</string>
-    <string name="download_path_title">Thư mục băng hình tải về</string>
-    <string name="download_path_summary">Băng hình đã tải về được lưu ở đây</string>
-    <string name="download_path_dialog_title">Chọn thư mục tải xuống cho các tệp băng hình</string>
+    <string name="download_path_title">Thư mục video tải về</string>
+    <string name="download_path_summary">Video đã tải về được lưu ở đây</string>
+    <string name="download_path_dialog_title">Chọn thư mục tải xuống cho các tệp video</string>
     <string name="download_path_audio_title">Thư mục tải xuống âm thanh</string>
     <string name="download_path_audio_summary">Các tệp âm thanh đã tải xuống được lưu trữ tại đây</string>
     <string name="download_path_audio_dialog_title">Chọn thư mục tải xuống cho các tệp âm thanh</string>
     <string name="default_resolution_title">Độ phân giải mặc định</string>
     <string name="default_popup_resolution_title">Độ phân giải cửa sổ bật lên mặc định</string>
     <string name="show_higher_resolutions_title">Hiện độ phân giải cao hơn</string>
-    <string name="show_higher_resolutions_summary">Chỉ một số thiết bị có thể phát băng hình 2K/4K</string>
+    <string name="show_higher_resolutions_summary">Chỉ một số thiết bị có thể phát video 2K/4K</string>
     <string name="play_with_kodi_title">Phát với Kodi</string>
     <string name="kore_not_found">Cài đặt ứng dụng Kore bị thiếu\?</string>
     <string name="show_play_with_kodi_title">Hiển thị tùy chọn \"Phát với Kodi\"</string>
-    <string name="show_play_with_kodi_summary">Hiển thị tùy chọn phát băng hình qua trung tâm truyền thông Kodi</string>
+    <string name="show_play_with_kodi_summary">Hiển thị tùy chọn phát video qua trung tâm truyền thông Kodi</string>
     <string name="play_audio">Âm thanh</string>
     <string name="default_audio_format_title">Định dạng âm thanh mặc định</string>
-    <string name="default_video_format_title">Định dạng băng hình mặc định</string>
+    <string name="default_video_format_title">Định dạng video mặc định</string>
     <string name="controls_background_title">Nền</string>
     <string name="theme_title">Chủ đề</string>
     <string name="light_theme_title">Sáng</string>
@@ -44,7 +44,7 @@
     <string name="show_search_suggestions_title">Đề xuất tìm kiếm</string>
     <string name="show_search_suggestions_summary">Chọn các đề xuất để hiển thị khi tìm kiếm</string>
     <string name="download_dialog_title">Tải về</string>
-    <string name="show_next_and_similar_title">Hiện các cuộn băng \"Tiếp theo\" và \"Tương tự\"</string>
+    <string name="show_next_and_similar_title">Hiện các video \"Tiếp theo\" và \"Tương tự\"</string>
     <string name="unsupported_url">URL không hỗ trợ</string>
     <string name="settings_category_appearance_title">Vẻ ngoài</string>
     <string name="background_player_playing_toast">Đang phát trong nền</string>
@@ -57,7 +57,7 @@
     <string name="error_report_title">Báo lỗi</string>
     <string name="all">Tất cả</string>
     <string name="disabled">Vô hiệu</string>
-    <string name="clear">Dọn dẹp</string>
+    <string name="clear">Xóa</string>
     <string name="best_resolution">Độ phân giải tốt nhất</string>
     <string name="general_error">Lỗi</string>
     <string name="network_error">Lỗi kết nối mạng</string>
@@ -65,7 +65,7 @@
     <string name="parsing_error">Không thể phân tích cú pháp trang web</string>
     <string name="content_not_available">Nội dung không khả dụng</string>
     <string name="could_not_setup_download_menu">Không thể thiết lập menu tải về</string>
-    <string name="app_ui_crash">Ứng dụng / Giao diện người dùng bị lỗi</string>
+    <string name="app_ui_crash">Ứng dụng/Giao diện người dùng bị lỗi</string>
     <string name="sorry_string">Có vẻ NewPipe đã xảy ra lỗi, lướt xuống kiểm tra xem.</string>
     <string name="error_report_button_text">Báo lỗi qua email</string>
     <string name="error_snackbar_message">Rất tiếc, đã xảy ra lỗi rồi.</string>
@@ -75,11 +75,11 @@
     <string name="info_labels">Loại lỗi:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành:</string>
     <string name="your_comment">Nhận xét của bạn (bằng tiếng Anh):</string>
     <string name="error_details_headline">Chi tiết:</string>
-    <string name="detail_thumbnail_view_description">Phát băng hình, thời lượng:</string>
+    <string name="detail_thumbnail_view_description">Phát video, thời lượng:</string>
     <string name="detail_uploader_thumbnail_view_description">Hình thu nhỏ của avatar người tải lên</string>
     <string name="detail_likes_img_view_description">Lượt thích</string>
     <string name="detail_dislikes_img_view_description">Lượt không thích</string>
-    <string name="video">Băng hình</string>
+    <string name="video">Video</string>
     <string name="audio">Âm thanh</string>
     <string name="retry">Thử lại</string>
     <string name="short_thousand">nghìn</string>
@@ -95,9 +95,9 @@
     <string name="msg_error">Lỗi</string>
     <string name="msg_running">NewPipe đang tải xuống</string>
     <string name="msg_running_detail">Chạm để biết chi tiết</string>
-    <string name="msg_wait">Đợi chút xíu nha…</string>
+    <string name="msg_wait">Vui lòng chờ…</string>
     <string name="msg_copied">Đã sao chép vào khay nhớ tạm</string>
-    <string name="no_available_dir">Hãy chọn một thư mục tải xuống trong phần thiết đặt</string>
+    <string name="no_available_dir">Vui lòng xác định thư mục tải xuống sau trong cài đặt</string>
     <string name="msg_popup_permission">Sự cho phép này là cần thiết để
 \nmở trong chế độ bật lên</string>
     <string name="title_activity_recaptcha">reCAPTCHA</string>
@@ -114,7 +114,7 @@
     <string name="read_full_license">Đọc giấy phép</string>
     <string name="contribution_title">Đóng góp</string>
     <string name="content_language_title">Ngôn ngữ nội dung ưu tiên</string>
-    <string name="settings_category_video_audio_title">Băng hình và âm thanh</string>
+    <string name="settings_category_video_audio_title">Video và âm thanh</string>
     <string name="enable_watch_history_title">Lịch sử xem</string>
     <string name="settings_category_history_title">Lịch sử và bộ nhớ đệm</string>
     <string name="search_no_results">Không tìm thấy</string>
@@ -133,18 +133,18 @@
     <string name="use_inexact_seek_title">Dùng tua nhanh ít chính xác</string>
     <string name="use_inexact_seek_summary">Tua ít chính xác cho phép trình phát giảm độ chính xác để tua tới vị trí nhanh hơn. Tua khoảng 5, 15 hoặc 25 giây không hoạt động với điều này</string>
     <string name="thumbnail_cache_wipe_complete_notice">Đã xóa bộ nhớ cache hình ảnh</string>
-    <string name="metadata_cache_wipe_title">Lau sạch siêu dữ liệu đã lưu đệm</string>
-    <string name="metadata_cache_wipe_summary">Loại bỏ mọi dữ liệu trang web đã lưu đệm</string>
+    <string name="metadata_cache_wipe_title">Xóa sạch siêu dữ liệu đã lưu đệm</string>
+    <string name="metadata_cache_wipe_summary">Xóa bỏ mọi dữ liệu trang web đã lưu đệm</string>
     <string name="metadata_cache_wipe_complete_notice">Đã xóa bộ nhớ cache siêu dữ liệu</string>
     <string name="auto_queue_title">Tự động xếp hàng luồng phát tiếp theo</string>
     <string name="auto_queue_summary">Tiếp tục hàng đợi (không lặp lại) bằng cách thêm một luồng phát liên quan</string>
     <string name="enable_search_history_title">Lịch sử tìm kiếm</string>
     <string name="enable_search_history_summary">Lưu trữ truy vấn tìm kiếm cục bộ</string>
-    <string name="enable_watch_history_summary">Theo dõi các cuộn băng đã xem</string>
+    <string name="enable_watch_history_summary">Theo dõi các video đã xem</string>
     <string name="resume_on_audio_focus_gain_title">Tiếp tục đang phát</string>
     <string name="resume_on_audio_focus_gain_summary">Tiếp tục phát lại sau khi bị gián đoạn (ví dụ: cuộc gọi)</string>
     <string name="show_hold_to_append_title">Hiển thị mẹo \"Giữ để xếp hàng\"</string>
-    <string name="show_hold_to_append_summary">Hiển thị mẹo khi nhấn vào nút nền hoặc nút bật lên trong \"Chi tiết:\" cuốn băng</string>
+    <string name="show_hold_to_append_summary">Hiển thị mẹo khi nhấn vào nút nền hoặc nút bật lên trong \"Chi tiết:\" video</string>
     <string name="default_content_country_title">Quốc gia nội dung mặc định</string>
     <string name="settings_category_player_title">Trình phát</string>
     <string name="settings_category_player_behavior_title">Hành vi</string>
@@ -163,16 +163,16 @@
     <string name="unknown_content">[Không xác định]</string>
     <string name="switch_to_background">Chuyển sang nền</string>
     <string name="switch_to_popup">Chuyển sang Cửa sổ bật lên</string>
-    <string name="switch_to_main">Chuyển sang Main</string>
+    <string name="switch_to_main">Chuyển sang Chính</string>
     <string name="import_data_title">Nhập cơ sở dữ liệu</string>
     <string name="export_data_title">Xuất cơ sở dữ liệu</string>
-    <string name="import_data_summary">Ghi đè lịch sử, đăng ký, danh sách phát và các thiết đặt (tùy chọn) hiện tại của bạn</string>
-    <string name="export_data_summary">Xuất lịch sử, đăng ký, danh sách phát và các thiết đặt</string>
-    <string name="clear_views_history_title">Dọn dẹp lịch sử xem</string>
+    <string name="import_data_summary">Ghi đè lịch sử, đăng ký, danh sách phát và các cài đặt (tùy chọn) hiện tại của bạn</string>
+    <string name="export_data_summary">Xuất lịch sử, đăng ký, danh sách phát và các cài đặt</string>
+    <string name="clear_views_history_title">Xóa lịch sử xem</string>
     <string name="clear_views_history_summary">Xóa lịch sử các luồng đã phát và các vị trí phát lại</string>
     <string name="delete_view_history_alert">Xóa toàn bộ lịch sử xem\?</string>
     <string name="watch_history_deleted">Đã xoá lịch sử xem</string>
-    <string name="clear_search_history_title">Dọn dẹp lịch sử tìm kiếm</string>
+    <string name="clear_search_history_title">Xóa lịch sử tìm kiếm</string>
     <string name="clear_search_history_summary">Xóa lịch sử tìm kiếm mà bạn đã ghi</string>
     <string name="delete_search_history_alert">Xóa toàn bộ lịch sử tìm kiếm\?</string>
     <string name="search_history_deleted">Đã xóa lịch sử tìm kiếm</string>
@@ -180,11 +180,11 @@
     <string name="player_unrecoverable_failure">Đã xảy ra lỗi trình phát không thể khôi phục</string>
     <string name="player_recoverable_failure">Phục hồi lại trình phát bị lỗi</string>
     <string name="external_player_unsupported_link_type">Trình phát ngoài không hỗ trợ các loại liên kết này</string>
-    <string name="video_streams_empty">Không tìm thấy luồng băng hình nào</string>
+    <string name="video_streams_empty">Không tìm thấy luồng video nào</string>
     <string name="audio_streams_empty">Không tìm thấy luồng âm thanh nào</string>
     <string name="invalid_directory">Thư mục không hợp lệ</string>
-    <string name="invalid_source">Tệp / nguồn nội dung không hợp lệ</string>
-    <string name="invalid_file">Tệp không tồn tại hoặc không có quyền đọc / ghi</string>
+    <string name="invalid_source">Tệp/nguồn nội dung không hợp lệ</string>
+    <string name="invalid_file">Tệp không tồn tại hoặc không có quyền đọc/ghi</string>
     <string name="file_name_empty_error">Tên tệp không được để trống</string>
     <string name="error_occurred_detail">Đã xảy ra lỗi: %1$s</string>
     <string name="no_streams_available_download">Không có luồng nào để tải về</string>
@@ -192,17 +192,17 @@
     <string name="detail_drag_description">Kéo để sắp xếp lại</string>
     <string name="no_subscribers">Không có người đăng ký</string>
     <plurals name="subscribers">
-        <item quantity="other">%s người đăng kí</item>
+        <item quantity="other">%s người đăng ký</item>
     </plurals>
     <string name="no_views">Không có lượt xem nào</string>
     <plurals name="views">
         <item quantity="other">%s lượt xem</item>
     </plurals>
-    <string name="no_videos">Không có cuộn băng nào</string>
+    <string name="no_videos">Không có video nào</string>
     <plurals name="videos">
-        <item quantity="other">%s cuộn băng</item>
+        <item quantity="other">%s video</item>
     </plurals>
-    <string name="create">Tạo nên</string>
+    <string name="create">Tạo</string>
     <string name="dismiss">Bỏ qua</string>
     <string name="rename">Đổi tên</string>
     <string name="one_item_deleted">Đã xóa 1 mục.</string>
@@ -239,12 +239,12 @@
     <string name="no_valid_zip_file">Không có tệp ZIP hợp lệ</string>
     <string name="could_not_import_all_files">Cảnh báo: Không thể nhập tất cả các tệp.</string>
     <string name="override_current_data">Thao tác này sẽ ghi đè cài đặt hiện tại của bạn.</string>
-    <string name="import_settings">Bạn có muốn cũng nhập các thiết đặt không\?</string>
+    <string name="import_settings">Bạn có muốn cũng nhập các cài đặt không?</string>
     <string name="trending">Thịnh hành</string>
-    <string name="new_and_hot">Mới và đang hot</string>
+    <string name="new_and_hot">Mới và nóng</string>
     <string name="play_queue_remove">Loại bỏ</string>
     <string name="play_queue_stream_detail">Chi tiết</string>
-    <string name="play_queue_audio_settings">Thiết đặt âm thanh</string>
+    <string name="play_queue_audio_settings">Cài đặt âm thanh</string>
     <string name="hold_to_append">Giữ để xếp hàng</string>
     <string name="start_here_on_background">Bắt đầu phát từ đây trong nền</string>
     <string name="start_here_on_popup">Bắt đầu phát trong cửa sổ bật lên</string>
@@ -252,7 +252,7 @@
     <string name="drawer_close">Đóng ngăn</string>
     <string name="preferred_open_action_settings_title">Hành động \'mở\' được ưu tiên</string>
     <string name="preferred_open_action_settings_summary">Hành động mặc định khi mở nội dung — %s</string>
-    <string name="video_player">Trình phát băng hình</string>
+    <string name="video_player">Trình phát video</string>
     <string name="background_player">Trình phát nền</string>
     <string name="popup_player">Trình phát bật lên</string>
     <string name="always_ask_open_action">Luôn luôn hỏi</string>
@@ -276,8 +276,8 @@
     <string name="caption_auto_generated">Tự động tạo ra</string>
     <string name="caption_setting_title">Phụ đề</string>
     <string name="caption_setting_description">Thay đổi tỷ lệ văn bản và kiểu nền phụ đề trình phát. Yêu cầu khởi động lại ứng dụng để có hiệu lực</string>
-    <string name="enable_leak_canary_summary">Theo dõi rò rỉ bộ nhớ có thể khiến ứng dụng trở nên không phản hồi khi đổ xô đống</string>
-    <string name="enable_disposed_exceptions_title">Báo các lỗi out-of-lifecycle</string>
+    <string name="enable_leak_canary_summary">Giám sát rò rỉ bộ nhớ có thể khiến ứng dụng không phản hồi khi tải vùng lưu trữ</string>
+    <string name="enable_disposed_exceptions_title">Báo cáo lỗi ngoài vòng đời</string>
     <string name="enable_disposed_exceptions_summary">Buộc báo cáo ngoại lệ Rx không thể gửi được bên ngoài vòng đời của mảnh hoặc hoạt động sau khi xử lý</string>
     <string name="import_title">Nhập</string>
     <string name="import_from">Nhập từ</string>
@@ -292,10 +292,10 @@
 \n
 \n1. Vào URL này: %1$s
 \n2. Đăng nhập khi được yêu cầu
-\n3. Nhấn chọn \"Bao gồm tất cả dữ liệu trên YouTube\", sau đó nhấn \"Bỏ chọn tất cả\", sau đó chỉ chọn mục \"đăng kí\" rồi nhấn OK
+\n3. Nhấn chọn \"Bao gồm tất cả dữ liệu trên YouTube\", sau đó nhấn \"Bỏ chọn tất cả\", sau đó chỉ chọn mục \"đăng ký\" rồi nhấn OK
 \n4. Nhấn nút \"Bước tiếp theo\" rồi nhấn \"Tạo tệp xuất\"
 \n5. Nhấn nút \"Tải xuống\" khi nó xuất hiện
-\n6. Từ file zip mới tải về, trích xuất file .json ra (thường nằm ở đường dẫn \"YouTube và YouTube Music/đăng kí/subscriptions.json\") rồi nhập vào đây.
+\n6. Từ file zip mới tải về, trích xuất file .json ra (thường nằm ở đường dẫn \"YouTube và YouTube Music/đăng ký/subscriptions.json\") rồi nhập vào đây.
 \n7. [Nếu nhập file .zip không thành công] Hãy giải nén tệp .csv (thường nó được để dưới phần \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), nhấn vào nút NHẬP TỆP ở phía bên dưới rồi chọn tệp csv đã được giải nén</string>
     <string name="import_soundcloud_instructions">Để nhập hồ sơ SoundCloud bằng cách nhập URL hoặc ID của bạn, hãy làm các bước như sau:
 \n
@@ -309,7 +309,7 @@
     <string name="playback_speed_control">Điều khiển tốc độ phát lại</string>
     <string name="playback_tempo">Tốc độ</string>
     <string name="playback_pitch">Độ cao</string>
-    <string name="unhook_checkbox">Bỏ gắn (có thể gây méo nhưng vui)</string>
+    <string name="unhook_checkbox">Bỏ gắn (có thể gây méo)</string>
     <string name="skip_silence_checkbox">Tua nhanh trong im lặng</string>
     <string name="playback_step">Bước</string>
     <string name="playback_reset">Đặt lại</string>
@@ -320,7 +320,7 @@
     <string name="limit_data_usage_none_description">Không giới hạn</string>
     <string name="limit_mobile_data_usage_title">Giới hạn độ phân giải khi sử dụng 3G, 4G</string>
     <string name="minimize_on_exit_title">Thu nhỏ khi chuyển qua ứng dụng khác</string>
-    <string name="minimize_on_exit_summary">Hành động khi chuyển sang ứng dụng khác từ trình phát băng hình chính — %s</string>
+    <string name="minimize_on_exit_summary">Hành động khi chuyển sang ứng dụng khác từ trình phát video chính — %s</string>
     <string name="minimize_on_exit_none_description">Không</string>
     <string name="minimize_on_exit_background_description">Thu nhỏ xuống trình phát nền</string>
     <string name="minimize_on_exit_popup_description">Thu nhỏ xuống trình phát bật lên</string>
@@ -385,13 +385,13 @@
     <string name="enable_playback_resume_summary">Khôi phục vị trí phát lại cuối cùng</string>
     <string name="enable_playback_state_lists_title">Vị trí phát trong danh sách</string>
     <string name="enable_playback_state_lists_summary">Hiển chỉ báo vị trí phát lại trong danh sách</string>
-    <string name="settings_category_clear_data_title">Dọn dẹp dữ liệu</string>
+    <string name="settings_category_clear_data_title">Xóa dữ liệu</string>
     <string name="watch_history_states_deleted">Đã xoá vị trí phát</string>
     <string name="missing_file">Tệp đã di chuyển hoặc đã xoá</string>
     <string name="overwrite_unrelated_warning">Tên file này đã tồn tại</string>
     <string name="overwrite_failed">Không thể ghi đè lên tệp</string>
-    <string name="download_already_pending">Có một bản tải xuống đang chờ xử lí với tên này</string>
-    <string name="error_postprocessing_stopped">Newpipe đã bị đóng khi đang xử lí tệp</string>
+    <string name="download_already_pending">Có một bản tải xuống đang chờ xử lý với tên này</string>
+    <string name="error_postprocessing_stopped">NewPipe đã bị đóng khi đang xử lý tệp</string>
     <string name="error_insufficient_storage_left">Không đủ dung lượng trên máy</string>
     <string name="error_progress_lost">Quá trình tải bị hủy, vì tập tin đã bị xoá</string>
     <string name="error_timeout">Kết nối hết thời gian</string>
@@ -425,10 +425,10 @@
     <plurals name="seconds">
         <item quantity="other">%d giây</item>
     </plurals>
-    <string name="remove_watched_popup_yes_and_partially_watched_videos">Có, và cuốn băng đã xem một phần</string>
-    <string name="remove_watched_popup_warning">Những cuốn băng đã xem trước và sau khi thêm vào danh sách phát sẽ bị loại bỏ.
-\nBạn có chắc không\? Điều này không thể được hoàn tác!</string>
-    <string name="remove_watched_popup_title">Loại bỏ các cuốn băng đã xem\?</string>
+    <string name="remove_watched_popup_yes_and_partially_watched_videos">Có, và video đã xem một phần</string>
+    <string name="remove_watched_popup_warning">Những video đã xem trước và sau khi thêm vào danh sách phát sẽ bị xóa.
+\nBạn có chắc không? Điều này không thể được hoàn tác!</string>
+    <string name="remove_watched_popup_title">Xóa video đã xem?</string>
     <string name="remove_watched">Xóa đã xem</string>
     <string name="systems_language">Mặc định hệ thống</string>
     <string name="app_language_title">Ngôn ngữ ứng dụng</string>
@@ -444,8 +444,8 @@
     <string name="localization_changes_requires_app_restart">Ngôn ngữ sẽ thay đổi khi ứng dụng khởi động lại</string>
     <string name="subtitle_activity_recaptcha">Bấm \"Xong\" khi hoàn thành</string>
     <string name="done">Đã hoàn thành</string>
-    <string name="infinite_videos">∞ cuộn băng</string>
-    <string name="more_than_100_videos">100+ cuốn băng</string>
+    <string name="infinite_videos">∞ video</string>
+    <string name="more_than_100_videos">100+ video</string>
     <plurals name="listening">
         <item quantity="other">%s người nghe</item>
     </plurals>
@@ -458,17 +458,17 @@
     <string name="artists">Nghệ sĩ</string>
     <string name="albums">Album</string>
     <string name="songs">Bài hát</string>
-    <string name="videos_string">Các cuốn băng</string>
-    <string name="restricted_video">Cuốn băng này bị giới hạn độ tuổi.
+    <string name="videos_string">Các video</string>
+    <string name="restricted_video">Video này bị giới hạn độ tuổi.
 \n
-\nBật \"%1$s\" trong thiết đặt nếu bạn muốn xem cuốn băng này.</string>
-    <string name="youtube_restricted_mode_enabled_title">Bật chế độ hạn chế Youtube</string>
+\nBật \"%1$s\" trong cài đặt nếu bạn muốn xem video này.</string>
+    <string name="youtube_restricted_mode_enabled_title">Bật chế độ hạn chế YouTube</string>
     <string name="peertube_instance_add_https_only">Chỉ URL HTTPS được hỗ trợ</string>
     <string name="peertube_instance_url_summary">Chọn thực thể PeerTube ưa thích</string>
     <string name="peertube_instance_url_title">Thực thể PeerTube</string>
     <string name="seek_duration_title">Thời lượng tua-nhanh tới/-lùi</string>
     <string name="show_original_time_ago_summary">Dòng chữ mô tả thời gian gốc từ các dịch vụ sẽ được hiển thị thay thế</string>
-    <string name="show_original_time_ago_title">Hiện thời gian gốc trên các item</string>
+    <string name="show_original_time_ago_title">Hiển thị thời gian ban đầu trước đây trên các mục</string>
     <string name="drawer_header_description">Chọn dịch vụ; dịch vụ hiện tại:</string>
     <string name="video_detail_by">Bởi %s</string>
     <string name="channel_created_by">Được tạo bởi %s</string>
@@ -476,13 +476,13 @@
     <string name="content_not_supported">NewPipe chưa hỗ trợ loại nội dung này.
 \n
 \nCó thể nó sẽ được hỗ trợ bởi một phiên bản mới hơn trong tương lai.</string>
-    <string name="feed_use_dedicated_fetch_method_help_text">Bạn có nghĩ rằng tải nguồn cấp dữ liệu quá chậm\? Nếu vậy, hãy thử bật tải nhanh (bạn có thể thay đổi nó này trong thiết đặt hoặc bằng cách nhấn nút bên dưới).
+    <string name="feed_use_dedicated_fetch_method_help_text">Bạn có nghĩ rằng tải nguồn cấp dữ liệu quá chậm? Nếu vậy, hãy thử bật tải nhanh (bạn có thể thay đổi nó này trong cài đặt hoặc bằng cách nhấn nút bên dưới).
 \n
 \nNewPipe cung cấp hai chiến lược tải nguồn cấp dữ liệu:
 \n• Tìm nạp toàn bộ kênh đăng ký, tuy chậm nhưng đầy đủ.
 \n• Sử dụng điểm cuối dịch vụ chuyên dụng, nhanh nhưng thường không hoàn thiện.
 \n
-\nSự khác biệt giữa hai loại này là cái nào nhanh thường thiếu một số thông tin, chẳng hạn như thời lượng hoặc loại mục (không thể phân biệt giữa các cuốn băng trực tiếp và bình thường) và nó có thể trả về ít mục hơn.
+\nSự khác biệt giữa hai loại này là cái nào nhanh thường thiếu một số thông tin, chẳng hạn như thời lượng hoặc loại mục (không thể phân biệt giữa các video trực tiếp và bình thường) và nó có thể trả về ít mục hơn.
 \n
 \nYouTube là một ví dụ về dịch vụ cung cấp phương pháp nhanh này với nguồn cấp RSS.
 \n
@@ -523,7 +523,7 @@
     <string name="error_report_open_github_notice">Vui lòng kiểm tra xem vấn đề mà bạn đang gặp đã báo cáo trước đó hay chưa. Nếu bạn tạo quá nhiều báo cáo trùng lặp, bạn sẽ khiến cho chúng tôi tốn thời gian để đọc chúng thay vì sửa lỗi bạn gặp.</string>
     <string name="error_report_open_issue_button_text">Báo cáo trên GitHub</string>
     <string name="copy_for_github">Sao chép bản báo cáo đã được định dạng</string>
-    <string name="unsupported_url_dialog_message">Không thể đọc URL này. Mở với app khác\?</string>
+    <string name="unsupported_url_dialog_message">Không thể đọc URL này. Mở với ứng dụng khác?</string>
     <string name="auto_queue_toggle">Tự động xếp hàng</string>
     <string name="clear_queue_confirmation_description">Hàng đợi của trình phát hiện tại sẽ bị thay thế</string>
     <string name="clear_queue_confirmation_summary">Việc chuyển từ trình phát này sang trình phát khác có thể sẽ thay thế hàng đợi</string>
@@ -539,7 +539,7 @@
     <string name="notification_action_2_title">Nút hành động thứ ba</string>
     <string name="notification_action_1_title">Nút hành động thứ hai</string>
     <string name="notification_action_0_title">Nút hành động đầu tiên</string>
-    <string name="notification_scale_to_square_image_summary">Cắt bớt hình thu nhỏ cuộn băng hiển thị trong thông báo từ tỷ lệ khung hình 16:9 xuống 1:1</string>
+    <string name="notification_scale_to_square_image_summary">Cắt bớt hình thu nhỏ video hiển thị trong thông báo từ tỷ lệ khung hình 16:9 xuống 1:1</string>
     <string name="notification_scale_to_square_image_title">Chỉnh ảnh thu nhỏ thành tỉ lệ 1:1</string>
     <string name="search_showing_result_for">Đang hiện kết quả cho: %s</string>
     <string name="enqueue_stream">Xếp hàng</string>
@@ -550,7 +550,7 @@
     <string name="enqueued">Đã xếp hàng</string>
     <string name="clear_cookie_summary">Xoá Cookie mà NewPipe lưu trữ sau khi bạn hoàn thành nó</string>
     <string name="recaptcha_cookies_cleared">Cookie reCAPTCHA đã được xóa</string>
-    <string name="clear_cookie_title">Dọn dẹp Cookie của reCAPCHA</string>
+    <string name="clear_cookie_title">Xóa Cookie của reCAPCHA</string>
     <string name="youtube_restricted_mode_enabled_summary">YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung có khả năng dành cho người trưởng thành</string>
     <string name="notification_colorize_summary">Yêu cầu Android tùy chỉnh màu của thông báo theo màu chính của ảnh thu nhỏ (lưu ý rằng việc này không khả dụng trên tất cả thiết bị)</string>
     <string name="notification_colorize_title">Tô màu thông báo</string>
@@ -561,11 +561,11 @@
     <string name="description_tab_description">Mô tả</string>
     <string name="related_items_tab_description">Các mục liên quan</string>
     <string name="comments_tab_description">Bình luận</string>
-    <string name="hash_channel_description">Thông báo cho quá trình băm cuốn băng</string>
-    <string name="hash_channel_name">Thông báo băm cuộn băng</string>
-    <string name="show_meta_info_summary">Tắt để ẩn các hộp siêu thông tin có thông tin bổ sung về người tạo luồng, nội dung luồng hoặc yêu cầu tìm kiếm</string>
-    <string name="show_meta_info_title">Hiển thị siêu thông tin</string>
-    <string name="show_description_summary">Tắt để ẩn mô tả cuộn băng và các thông tin bổ sung</string>
+    <string name="hash_channel_description">Thông báo cho quá trình băm video</string>
+    <string name="hash_channel_name">Thông báo băm video</string>
+    <string name="show_meta_info_summary">Tắt để ẩn các hộp thông tin meta có thông tin bổ sung về người tạo luồng, nội dung luồng hoặc yêu cầu tìm kiếm</string>
+    <string name="show_meta_info_title">Hiển thị thông tin meta</string>
+    <string name="show_description_summary">Tắt để ẩn mô tả video và các thông tin bổ sung</string>
     <string name="show_description_title">Hiện mô tả</string>
     <string name="open_with">Mở bằng</string>
     <string name="download_has_started">Tệp đang được tải xuống</string>
@@ -574,18 +574,18 @@
     <string name="auto_device_theme_title">Tự động (giao diện hệ thống)</string>
     <string name="radio">Radio</string>
     <string name="paid_content">Nội dung này chỉ dành cho người dùng trả phí, nên NewPipe không thể phát hay tải xuống.</string>
-    <string name="youtube_music_premium_content">Cuốn băng này chỉ được dành cho thành viên YouTube Music Premium, nên NewPipe không thể phát hay tải xuống.</string>
+    <string name="youtube_music_premium_content">Video này chỉ được dành cho thành viên YouTube Music Premium, nên NewPipe không thể phát hay tải xuống.</string>
     <string name="private_content">Nội dung này được để ở chế độ riêng tư, nên NewPipe không thể phát hay tải xuống.</string>
-    <string name="soundcloud_go_plus_content">Đây là một track SoundCloud Go+, nên NewPipe không thể phát hay tải xuống được, ít nhất là tại quốc gia của bạn.</string>
+    <string name="soundcloud_go_plus_content">Đây là bản nhạc SoundCloud Go+, ít nhất là ở quốc gia của bạn, vì vậy NewPipe không thể phát trực tuyến hoặc tải xuống bản nhạc này.</string>
     <string name="georestricted_content">Nội dung này không có sẵn ở quốc gia của bạn.</string>
     <string name="crash_the_app">Làm văng ứng dụng</string>
-    <string name="restricted_video_no_stream">Cuốn băng này bị giới hạn độ tuổi.
-\nDo chính sách mới của YouTube với các cuốn băng bị giới hạn độ tuổi, NewPipe không thể truy cập bất kỳ luồng băng hình nào của nó và do đó không thể phát nó.</string>
+    <string name="restricted_video_no_stream">Video này bị giới hạn độ tuổi.
+\nDo chính sách mới của YouTube với các video bị giới hạn độ tuổi, NewPipe không thể truy cập bất kỳ luồng video nào của nó và do đó không thể phát nó.</string>
     <string name="night_theme_title">Chủ đề đêm</string>
     <string name="featured">Nổi bật</string>
     <string name="show_channel_details">Hiện chi tiết kênh</string>
     <string name="recaptcha_solve">Hoàn thành</string>
-    <string name="disable_media_tunneling_summary">Tắt tính năng truyền tải phương tiện nếu bạn gặp phải tình trạng màn hình đen hoặc giật hình khi phát lại cuốn băng.</string>
+    <string name="disable_media_tunneling_summary">Tắt tính năng truyền tải phương tiện nếu bạn gặp phải tình trạng màn hình đen hoặc giật hình khi phát lại video.</string>
     <string name="disable_media_tunneling_title">Tắt truyền phương tiện qua đường hầm</string>
     <string name="off">Tắt</string>
     <string name="on">Bật</string>
@@ -614,7 +614,7 @@
 \nBạn có muốn hủy đăng ký khỏi kênh này không\?</string>
     <string name="feed_load_error_account_info">Không thể tải nguồn cấp dữ liệu cho \'%s\'.</string>
     <string name="feed_load_error">Lỗi tải nguồn cấp dữ liệu</string>
-    <string name="downloads_storage_use_saf_summary_api_29">\'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi</string>
+    <string name="downloads_storage_use_saf_summary_api_29">\'Khung truy cập lưu trữ\' chỉ được hỗ trợ từ Android 10 trở lên</string>
     <string name="downloads_storage_ask_summary_no_saf_notice">Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống</string>
     <string name="no_dir_yet">Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay</string>
     <string name="dont_show">Không hiện</string>
@@ -635,11 +635,11 @@
         <item quantity="other">%s lượt tải xuống đã hoàn tất</item>
     </plurals>
     <string name="main_page_content_swipe_remove">Vuốt các mục để xóa chúng</string>
-    <string name="start_main_player_fullscreen_summary">Không bắt đầu các cuốn băng ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình</string>
+    <string name="start_main_player_fullscreen_summary">Không bắt đầu các video ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình</string>
     <string name="start_main_player_fullscreen_title">Khởi động trình phát chính ở toàn màn hình</string>
     <string name="enqueued_next">Đã xếp kế tiếp vào hàng</string>
     <string name="enqueue_next_stream">Xếp kế tiếp vào hàng</string>
-    <string name="processing_may_take_a_moment">Đang xử lý... Có thể hơi lâu</string>
+    <string name="processing_may_take_a_moment">Đang xử lý... Có thể mất chút thời gian</string>
     <string name="error_report_channel_name">Thông báo lỗi</string>
     <string name="error_report_channel_description">Thông báo để báo cáo lỗi</string>
     <string name="error_report_notification_title">NewPipe đã gặp sự cố, nhấn để xem và báo cáo</string>
@@ -684,11 +684,11 @@
     <string name="enumeration_comma">,</string>
     <string name="semitone">Nửa cung</string>
     <string name="streams_not_yet_supported_removed">Luồng băng hình mà không được trình tải xuống hỗ trợ sẽ không hiển thị</string>
-    <string name="no_video_streams_available_for_external_players">Không có luồng băng hình nào khả dụng cho trình phát bên ngoài</string>
+    <string name="no_video_streams_available_for_external_players">Không có luồng video nào khả dụng cho trình phát bên ngoài</string>
     <string name="selected_stream_external_player_not_supported">Luồng phát đã chọn không được trình phát ngoài hỗ trợ</string>
     <string name="no_audio_streams_available_for_external_players">Không có luồng âm thanh nào khả dụng cho máy phát bên ngoài</string>
     <string name="select_quality_external_players">Chọn chất lượng cho trình chạy ngoài</string>
-    <string name="unknown_format">Định dạng không xác định (:P)</string>
+    <string name="unknown_format">Định dạng không xác định</string>
     <string name="unknown_quality">Độ phân giải không xác định</string>
     <string name="progressive_load_interval_title">Kích thước tải thời lượng phát lại</string>
     <string name="sort">Thể loại</string>
@@ -717,9 +717,9 @@
     <string name="progressive_load_interval_summary">Thay đổi kích thước khoảng thời gian tải trên nội dung lũy tiến (hiện tại là %s). Giá trị thấp hơn có thể tăng tốc độ tải ban đầu của chúng</string>
     <string name="audio_track_present_in_video">Một bản âm thanh đã có sẵn trong luồng này</string>
     <string name="use_exoplayer_decoder_fallback_title">Sử dụng tính năng bộ giải mã dự phòng của ExoPlayer</string>
-    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Giải pháp thay thế này giải phóng và khởi tạo lại codec băng hình khi xảy ra thay đổi bề mặt, thay vì thiết đặt trực tiếp bề mặt vào codec. Đã được ExoPlayer sử dụng trên một số thiết bị gặp sự cố này, thiết đặt này chỉ ảnh hưởng đến Android 6 trở lên
+    <string name="always_use_exoplayer_set_output_surface_workaround_summary">Giải pháp thay thế này giải phóng và khởi tạo lại codec video khi xảy ra thay đổi bề mặt, thay vì cài đặt trực tiếp bề mặt vào codec. Đã được ExoPlayer sử dụng trên một số thiết bị gặp sự cố này, cài đặt này chỉ ảnh hưởng đến Android 6 trở lên
 \n
-\nBật tùy chọn này có thể ngăn lỗi phát lại khi chuyển đổi trình phát băng hình hiện tại hoặc chuyển sang chế độ toàn màn hình</string>
+\nBật tùy chọn này có thể ngăn lỗi phát lại khi chuyển đổi trình phát video hiện tại hoặc chuyển sang chế độ toàn màn hình</string>
     <string name="use_exoplayer_decoder_fallback_summary">Bật tùy chọn này nếu bạn gặp sự cố khởi tạo bộ giải mã, vấn đề này sẽ quay trở lại bộ giải mã có mức độ ưu tiên thấp hơn nếu quá trình khởi tạo bộ giải mã chính thất bại. Điều này có thể dẫn đến hiệu suất phát lại kém hơn so với khi sử dụng bộ giải mã chính</string>
     <string name="always_use_exoplayer_set_output_surface_workaround_title">Luôn dùng biện pháp thay thế cho bề mặt đầu ra video cho ExoPlayer</string>
     <string name="audio_track_type_original">nguyên gốc</string>
@@ -741,8 +741,8 @@
     <string name="feed_show_upcoming">Sắp tới</string>
     <string name="unknown_audio_track">Không rõ</string>
     <string name="select_audio_track_external_players">Chọn bản âm thanh cho máy phát bên ngoài</string>
-    <string name="settings_category_exoplayer_title">Thiết đặt ExoPlayer</string>
-    <string name="settings_category_exoplayer_summary">Quản lý một số thiết đặt ExoPlayer. Những thay đổi này yêu cầu khởi động lại trình phát để có hiệu lực</string>
+    <string name="settings_category_exoplayer_title">Cài đặt ExoPlayer</string>
+    <string name="settings_category_exoplayer_summary">Quản lý một số cài đặt ExoPlayer. Những thay đổi này yêu cầu khởi động lại trình phát để có hiệu lực</string>
     <string name="loading_metadata_title">Đang tải siêu dữ liệu…</string>
     <string name="main_tabs_position_title">Vị trí tab chính</string>
     <string name="feed_fetch_channel_tabs">Tìm nạp các tab kênh</string>
@@ -753,7 +753,7 @@
     <string name="no_live_streams">Không có luồng trực tiếp</string>
     <string name="metadata_thumbnails">Ảnh xem trước</string>
     <string name="no_streams">Không có luồng</string>
-    <string name="channel_tab_videos">Các cuộn băng</string>
+    <string name="channel_tab_videos">Các video</string>
     <string name="metadata_subscribers">Người đăng ký</string>
     <string name="show_channel_tabs_summary">Những thẻ nào được hiển thị trên các trang kênh</string>
     <string name="show_channel_tabs">Thẻ kênh</string>
@@ -763,7 +763,7 @@
     <string name="audio_track_name">%1$s %2$s</string>
     <string name="next_stream">Luồng tiếp theo</string>
     <string name="metadata_subchannel_avatars">Hình đại diện kênh phụ</string>
-    <string name="open_play_queue">Mở hàng chờ phát</string>
+    <string name="open_play_queue">Mở phát hàng chờ</string>
     <string name="channel_tab_about">Giới thiệu</string>
     <string name="replay">Phát lại</string>
     <string name="metadata_banners">Băng rôn</string>
@@ -787,7 +787,7 @@
     <string name="forward">Tua đi</string>
     <string name="channel_tab_albums">Album</string>
     <string name="rewind">Tua lại</string>
-    <string name="share_playlist_with_titles_message">Chia sẻ danh sách phát với các thông tin chi tiết như tên danh sách phát và tiêu đề video hoặc dưới dạng danh sách URL cuốn băng đơn giản</string>
+    <string name="share_playlist_with_titles_message">Chia sẻ danh sách phát với các thông tin chi tiết như tên danh sách phát và tiêu đề video hoặc dưới dạng danh sách URL video đơn giản</string>
     <string name="image_quality_medium">Chất lượng trung bình</string>
     <string name="video_details_list_item">- %1$s: %2$s</string>
     <string name="image_quality_summary">Chọn chất lượng hình ảnh và chọn có tải chất lượng ảnh hay không, để giảm mức sử dụng dữ liệu và bộ nhớ. Thay đổi xoá cache ảnh cho cả trong bộ nhớ lẫn ổ cứng - %s</string>
@@ -799,4 +799,15 @@
     <plurals name="replies">
         <item quantity="other">%s hồi đáp</item>
     </plurals>
+    <string name="error_insufficient_storage">Không đủ dung lượng trống trên thiết bị</string>
+    <string name="settings_category_backup_restore_title">Sao lưu và khôi phục</string>
+    <string name="reset_settings_title">Đặt lại cài đặt</string>
+    <string name="reset_settings_summary">Đặt lại tất cả cài đặt về giá trị mặc định</string>
+    <string name="reset_all_settings">Việc đặt lại tất cả cài đặt sẽ loại bỏ tất cả các cài đặt ưa thích của bạn và khởi động lại ứng dụng.
+\n
+\nBạn có chắc muốn tiếp tục?</string>
+    <string name="no">Không</string>
+    <string name="auto_update_check_description">NewPipe có thể tự động kiểm tra các phiên bản mới theo thời gian và thông báo cho bạn khi chúng có sẵn.
+\nBạn có muốn kích hoạt tính năng này không?</string>
+    <string name="yes">Có</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 29b1fc302..fa8d31022 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -799,4 +799,15 @@
     <string name="show_more">显示较多</string>
     <string name="show_less">显示较少</string>
     <string name="notification_actions_summary_android13">轻按下方的每个通知操作进行编辑。头三个动作(播放/暂停、上一个和下一个)是系统设置的,不能自定义。</string>
+    <string name="error_insufficient_storage">设备剩余空间不足</string>
+    <string name="settings_category_backup_restore_title">备份和还原</string>
+    <string name="reset_settings_title">重置设置</string>
+    <string name="reset_settings_summary">将所有设置重置为默认值</string>
+    <string name="reset_all_settings">重置所有设置会取消你所有的偏好设置并重启应用。
+\n
+\n你确定想要继续吗?</string>
+    <string name="no">否</string>
+    <string name="auto_update_check_description">NewPipe 可以自动时不时地检查新版本并在新版本可用时通知你。
+\n你想开启该功能吗?</string>
+    <string name="yes">是</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index f293773bd..a59da9c4e 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -799,4 +799,5 @@
     <string name="show_less">摺埋</string>
     <string name="show_more">拉開</string>
     <string name="notification_actions_summary_android13">撳下面嘅掣去更改對應嘅通知動作。頭三個動作 (播放/暫停、上一個、下一個) 系統預設咗,冇得揀。</string>
+    <string name="error_insufficient_storage">部機冇晒位</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index cb9279c41..c404edeca 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -799,4 +799,15 @@
     <string name="show_more">顯示更多</string>
     <string name="show_less">顯示較少</string>
     <string name="notification_actions_summary_android13">透過點擊下面的每個通知操作來編輯它。前三個動作(播放/暫停、上一個與下一個)由系統設定,無法自訂。</string>
+    <string name="error_insufficient_storage">裝置上沒有足夠的空間</string>
+    <string name="settings_category_backup_restore_title">備份與還原</string>
+    <string name="reset_settings_title">重設設定</string>
+    <string name="reset_settings_summary">將所有設定重設為預設值</string>
+    <string name="reset_all_settings">重設所有設定將會放棄您的所有偏好設定並重新啟動應用程式。
+\n
+\n您確定您想要繼續嗎?</string>
+    <string name="yes">是</string>
+    <string name="no">否</string>
+    <string name="auto_update_check_description">NewPipe 可以隨時自動檢查新版本,並在新版本可用時通知您。
+\n您想要啟用此功能嗎?</string>
 </resources>
\ No newline at end of file
diff --git a/fastlane/metadata/android/hu/short_description.txt b/fastlane/metadata/android/hu/short_description.txt
index 50752eeaf..5e18acc4f 100644
--- a/fastlane/metadata/android/hu/short_description.txt
+++ b/fastlane/metadata/android/hu/short_description.txt
@@ -1 +1 @@
-Ingyenes, könnyű YouTube felület Androidra.
+Egy ingyenes, könnyű YouTube felület Androidra.
diff --git a/fastlane/metadata/android/ko/changelogs/65.txt b/fastlane/metadata/android/ko/changelogs/65.txt
new file mode 100644
index 000000000..5c2a302e1
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/65.txt
@@ -0,0 +1,27 @@
+### 개선사항
+
+- 버거 메뉴 아이콘 애니메이션 비활성화 #1486
+- 다운로드 삭제 취소 #1472
+- 공유 메뉴 #1498의 다운로드 옵션
+- 길게 탭하는 메뉴 #1454에 공유 옵션 추가
+- 1354번 출구에서 메인 플레이어 최소화
+- 라이브러리 버전 업데이트 및 데이터베이스 백업 수정 #1510
+- ExoPlayer 2.8.2 업데이트 #1392
+  - 더 빠른 속도 변경을 위해 다양한 단계 크기를 지원하도록 재생 속도 제어 대화 상자를 재작업 했습니다.
+  - 재생 속도 제어에서 무음 중에 빨리 감기하는 토글을 추가했습니다. 이는 오디오북과 특정 음악 장르에 도움이 될 것이며 진정한 원활한 경험을 제공할 수 있습니다(그리고 무음이 많은 노래를 깨뜨릴 수 있습니다 =\\).
+  - 수동으로 수행하는 대신 플레이어 내부에서 미디어와 함께 메타데이터를 전달할 수 있도록 미디어 소스 해상도를 리팩터링했습니다. 이제 단일 메타데이터 소스가 있으며 재생이 시작되면 바로 사용할 수 있습니다.
+  - 재생 목록 조각이 열릴 때 새 메타데이터를 사용할 수 있을 때 원격 재생 목록 메타데이터가 업데이트되지 않는 문제를 수정했습니다.
+  - 다양한 UI 수정: #1383, 백그라운드 플레이어 알림 컨트롤은 이제 항상 흰색이며, 플링을 통해 팝업 플레이어를 더 쉽게 종료할 수 있습니다.
+- 멀티서비스를 위해 리팩토링된 아키텍처를 갖춘 새로운 추출기 사용
+
+
+### 수정됨
+
+- #1440 깨진 비디오 정보 레이아웃 수정 #1491
+- 기록 수정 #1497 보기
+  - #1495, 사용자가 재생 목록에 액세스하는 즉시 메타데이터(썸네일, 제목 및 동영상 개수)를 업데이트합니다.
+  - #1475, 사용자가 디테일 프래그먼트의 외부 플레이어에서 비디오를 시작할 때 데이터베이스에 뷰를 등록합니다.
+- 팝업 모드의 경우 크린 시간 초과 문제를 수정합니다. #1463 (고정 #640)
+- 기본 비디오 플레이어 수정 #1509
+  - [#1412] 플레이어 활동이 백그라운드에 있는 동안 새로운 인텐트가 수신되면 반복 모드에서 플레이어 NPE가 발생하는 문제를 수정했습니다.
+  - 팝업 권한이 부여되지 않은 경우 플레이어를 팝업으로 최소화하면 플레이어가 삭제되지 않는 문제가 해결되었습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/66.txt b/fastlane/metadata/android/ko/changelogs/66.txt
new file mode 100644
index 000000000..430ff96e0
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/66.txt
@@ -0,0 +1,29 @@
+# v0.13.7의 변경 내역
+### 수정됨
+- v0.13.6의 정렬 필터 문제 수정
+
+# v0.13.6의 변경 내역
+### 개선 사항
+- 버거 메뉴 아이콘 애니메이션 비활성화 #1486
+- 다운로드 삭제 취소 #1472
+- 공유 메뉴 #1498의 다운로드 옵션
+- 길게 탭하는 메뉴 #1454에 공유 옵션 추가
+- 1354번 출구에서 메인 플레이어 최소화
+- 라이브러리 버전 업데이트 및 데이터베이스 백업 수정 #1510
+- ExoPlayer 2.8.2 업데이트 #1392
+  - 더 빠른 속도 변경을 위해 다양한 단계 크기를 지원하도록 재생 속도 제어 대화 상자를 재작업했습니다.
+  - 재생 속도 제어에서 무음 중에 빨리 감기하는 토글을 추가했습니다. 이는 오디오북과 특정 음악 장르에 도움이 될 것이며 진정한 원활한 경험을 제공할 수 있습니다(그리고 무음이 많은 노래를 깨뜨릴 수 있습니다 =\\).
+  - 수동으로 수행하는 대신 플레이어 내부에서 미디어와 함께 메타데이터를 전달할 수 있도록 미디어 소스 해상도를 리팩터링했습니다. 이제 단일 메타데이터 소스가 있으며 재생이 시작되면 바로 사용할 수 있습니다.
+  - 재생 목록 조각이 열릴 때 새 메타데이터를 사용할 수 있을 때 원격 재생 목록 메타데이터가 업데이트되지 않는 문제를 수정했습니다.
+  - 다양한 UI 수정: #1383, 백그라운드 플레이어 알림 컨트롤은 이제 항상 흰색이며, 플링을 통해 팝업 플레이어를 더 쉽게 종료할 수 있습니다.
+- 멀티서비스를 위해 리팩토링된 아키텍처를 갖춘 새로운 추출기 사용
+
+### 수정
+- #1440 깨진 비디오 정보 레이아웃 수정 #1491
+- 기록 수정 #1497 보기
+  - #1495, 사용자가 재생 목록에 액세스하는 즉시 메타데이터(썸네일, 제목 및 동영상 개수)를 업데이트합니다.
+  - #1475, 사용자가 디테일 프래그먼트의 외부 플레이어에서 비디오를 시작할 때 데이터베이스에 뷰를 등록합니다.
+- 팝업 모드의 경우 크린 시간 초과 문제를 수정합니다. #1463 (고정 #640)
+- 기본 비디오 플레이어 수정 #1509
+  - [#1412] 플레이어 활동이 백그라운드에 있는 동안 새로운 인텐트가 수신되면 반복 모드에서 플레이어 NPE가 발생하는 문제를 수정했습니다.
+  - 팝업 권한이 부여되지 않은 경우 플레이어를 팝업으로 최소화하면 플레이어가 삭제되지 않는 문제가 해결되었습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/68.txt b/fastlane/metadata/android/ko/changelogs/68.txt
new file mode 100644
index 000000000..77f8fad41
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/68.txt
@@ -0,0 +1,31 @@
+# v0.14.1의 변경 사항
+
+### 수정됨
+- 비디오 URL #1659를 해독하지 못하던 문제 수정
+- 설명 링크가 잘 추출되지 않던 문제 수정 #1657
+
+# v0.14.0의 변경 사항
+
+### 새로운
+- 새로운 서랍 디자인 #1461
+- 새로운 맞춤형 첫 페이지 #1461
+
+### 개선 사항
+- 재작업된 제스처 컨트롤 #1604
+- 팝업 플레이어를 닫는 새로운 방법 #1597
+
+### 수정됨
+- 구독 횟수를 확인할 수 없을 때 발생하는 오류를 수정합니다. #1649를 닫습니다.
+  - 이 경우 "구독자 수를 확인할 수 없음"을 표시합니다.
+- YouTube 재생목록이 비어 있을 때 NPE 수정
+- SoundCloud의 키오스크에 대한 빠른 수정
+- 리팩터링 및 버그 수정 #1623
+  - 순환 검색 결과 #1562 수정
+  - 탐색 막대가 정적으로 배치되지 않은 문제 수정
+  - YT Premium 비디오가 올바르게 차단되지 않는 문제 수정
+  - 가끔 비디오가 로드되지 않는 문제 수정(DASH 구문 분석으로 인해)
+  - 동영상 설명의 링크 수정
+  - 누군가 외부 SD 카드에 다운로드하려고 하면 경고 표시
+  - 아무것도 표시되지 않음 예외 트리거 보고서 수정
+  - Android 8.1용 백그라운드 플레이어에는 썸네일이 표시되지 않습니다. [여기 참조](https://github.com/TeamNewPipe/NewPipe/issues/943)
+- 방송수신기 등록 문제 수정. #1641을 닫습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/69.txt b/fastlane/metadata/android/ko/changelogs/69.txt
new file mode 100644
index 000000000..0083b5e58
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/69.txt
@@ -0,0 +1,19 @@
+### 새로운
+- 길게 탭하여 구독정보 삭제 및 공유 #1516
+- 태블릿 UI 및 그리드 목록 레이아웃 #1617
+
+### 개선 사항
+- 마지막으로 사용된 화면 비율 #1748을 저장하고 다시 로드합니다.
+- 전체 비디오 이름 #1771을 사용하여 다운로드 활동에서 선형 레이아웃을 활성화합니다.
+- 구독 탭 #1516 내에서 직접 구독 삭제 및 공유
+- 재생 대기열이 이미 종료된 경우 대기열에 추가하면 비디오 재생이 시작됩니다. #1783
+- 볼륨 및 밝기 제스처에 대한 별도 설정 #1644
+- 현지화 #1792 지원 추가
+
+### 수정
+- 에 대한 시간 구문 분석을 수정합니다. 형식이므로 핀란드에서 NewPipe를 사용할 수 있습니다.
+- 구독 수 수정
+- API 28+ 장치에 대한 포그라운드 서비스 권한 추가 #1830
+
+### 알려진 버그
+- Android P에서는 재생 상태를 저장할 수 없습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/70.txt b/fastlane/metadata/android/ko/changelogs/70.txt
new file mode 100644
index 000000000..15c41627f
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/70.txt
@@ -0,0 +1,25 @@
+주의: 이 버전은 아마도 지난 버전과 마찬가지로 버그페스트일 것입니다. 그러나 17. 이후 전체 종료로 인해 깨진 버전이 버전이 없는 것보다 낫습니다. 오른쪽? ̅\_(ツ)_/̅
+
+### 개선 사항
+* 이제 다운로드한 파일을 한 번의 클릭으로 열 수 있습니다 #1879
+* 안드로이드 4.1 - 4.3 #1884에 대한 지원 중단
+* 이전 플레이어 #1884 제거
+* 스트림을 오른쪽으로 스와이프하여 현재 재생 대기열에서 스트림을 제거합니다. #1915
+* 새 스트림이 수동으로 대기열에 추가되면 자동 대기열에 추가된 스트림을 제거합니다. #1878
+* 다운로드를 위한 후처리 및 누락된 기능 구현 #1759 by @kapodamy
+  * 후처리 인프라
+  * 적절한 오류 처리 "인프라"(다운로더용)
+  * 여러 다운로드 대신 대기열
+  * 직렬화된 보류 중인 다운로드(`.giga` 파일)를 앱 데이터로 이동
+  * 최대 다운로드 재시도 구현
+  * 적절한 멀티스레드 다운로드 일시중지
+  * 모바일 네트워크로 전환할 때 다운로드 중지(작동하지 않음, 두 번째 항목 참조)
+  * 다음 다운로드를 위해 스레드 수를 저장하세요
+  * 많은 불일치가 수정되었습니다.
+
+### 수정됨
+* 기본 해상도가 최고 및 제한된 모바일 데이터 해상도 #1835로 설정된 경우 충돌 수정
+* 팝업 플레이어 충돌 수정 #1874
+* 백그라운드 플레이어를 열려고 할 때 NPE #1901
+* 자동 대기열이 활성화된 경우 새 스트림을 삽입하는 문제 수정 #1878
+* 암호 해독 폐쇄 문제 수정
diff --git a/fastlane/metadata/android/ko/changelogs/71.txt b/fastlane/metadata/android/ko/changelogs/71.txt
new file mode 100644
index 000000000..c38a4e6c7
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/71.txt
@@ -0,0 +1,10 @@
+### 개선 사항
+* GitHub 빌드에 대한 앱 업데이트 알림 추가(@krtkush의 #1608)
+* 다운로더의 다양한 개선 사항(@kapodamy의 #1944):
+  * 누락된 흰색 아이콘을 추가하고 하드코어 방식을 사용하여 아이콘 색상을 변경합니다.
+  * 반복자가 초기화되었는지 확인하세요(수정 #2031)
+  * 새 먹서에서 "사후 처리 실패" 오류가 있는 다운로드 재시도 허용
+  * 비동기 비디오 및 오디오 스트림을 수정하는 새로운 MPEG-4 먹서(#2039)
+
+### 수정됨
+* YouTube 실시간 스트리밍은 잠시 후에 재생이 중지됩니다(@yausername의 #1996).
diff --git a/fastlane/metadata/android/ko/changelogs/730.txt b/fastlane/metadata/android/ko/changelogs/730.txt
new file mode 100644
index 000000000..b30d9896a
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/730.txt
@@ -0,0 +1,2 @@
+# 고정
+- 핫픽스 복호화 기능 오류가 다시 발생했습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/740.txt b/fastlane/metadata/android/ko/changelogs/740.txt
new file mode 100644
index 000000000..41c78eb2a
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/740.txt
@@ -0,0 +1,23 @@
+<h4>개선 사항</h4>
+<ul>
+<li>댓글의 링크를 클릭 가능하게 만들고 텍스트 크기를 늘립니다.</li>
+<li>댓글의 타임스탬프 링크 클릭</li>
+<li>최근 선택한 상태를 기준으로 기본 탭 표시</li>
+<li>재생목록 창에서 '배경'을 길게 클릭하면 대기열에 재생목록 추가</li>
+<li>URL이 아닌 공유 텍스트 검색</li>
+<li>기본 비디오 플레이어에 "현재 시간 공유" 버튼 추가</li>
+<li>동영상 대기열이 완료되면 기본 플레이어에 닫기 버튼 추가</li>
+<li>동영상 목록 항목의 길게 누르기 메뉴에 "백그라운드에서 직접 재생" 추가</li>
+<li>Play/Enqueue 명령에 대한 영어 번역 개선</li>
+<li>작은 성능 개선</li>
+<li>사용하지 않는 파일 제거</li>
+<li>ExoPlayer를 2.9.6으로 업데이트</li>
+<li>Invidious 링크에 대한 지원 추가</li>
+</ul>
+<h4>수정됨</h4>
+<ul>
+<li>댓글 및 관련 스트림이 비활성화된 고정 스크롤</li>
+<li>CheckForNewAppVersionTask가 실행되어서는 안 될 때 실행되는 문제를 수정했습니다.</li>
+<li>YouTube 구독 가져오기 수정: URL이 잘못된 구독은 무시하고 제목이 비어 있는 구독은 유지</li>
+<li>잘못된 YouTube URL 수정: 서명 태그 이름이 스트림 로드를 방해하는 '서명'이 아닐 수도 있습니다.</li>
+</ul>
diff --git a/fastlane/metadata/android/ko/changelogs/750.txt b/fastlane/metadata/android/ko/changelogs/750.txt
new file mode 100644
index 000000000..08450674b
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/750.txt
@@ -0,0 +1,22 @@
+새로운
+재생 재개 #2288
+• 지난번에 중단한 스트림 재개
+다운로더 개선 사항 #2149
+• 저장소 액세스 프레임워크를 사용하여 외부 SD 카드에 다운로드 저장
+• 새로운 mp4 먹서
+• 선택적으로 다운로드를 시작하기 전에 다운로드 디렉터리를 변경합니다.
+• 종량제 네트워크를 존중합니다.
+
+
+향상
+• gema 문자열 #2295 제거됨
+• 활동 수명주기 동안 (자동)회전 변경 처리 #2444
+• 길게 누르기 메뉴를 일관성있게 만들기 #2368
+
+결정된
+• 선택한 자막 트랙 이름이 표시되지 않는 문제 수정 #2394
+• 앱 업데이트 확인 실패 시 충돌 방지(GitHub 버전) #2423
+• 다운로드가 99.9%에서 멈추는 현상 수정 #2440
+• 재생 대기열 메타데이터 업데이트 #2453
+• [SoundCloud] 재생 목록을 로드할 때 발생하는 충돌 수정 TeamNewPipe/NewPipeExtractor#170
+• [YouTube] 고정 기간은 TeamNewPipe/NewPipeExtractor#177로 파싱할 수 없습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/760.txt b/fastlane/metadata/android/ko/changelogs/760.txt
new file mode 100644
index 000000000..78097b1ab
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/760.txt
@@ -0,0 +1,43 @@
+0.17.1의 변경 사항
+
+새로운
+• 태국어 현지화
+
+
+향상
+• 재생목록을 다시 길게 누르는 메뉴에 여기서 재생 시작 작업 추가 #2518
+• SAF/레거시 파일 선택기 #2521용 스위치 추가
+
+결정된
+• 앱 전환 시 다운로드 보기에서 버튼이 사라지는 문제 수정 #2487
+• 시청 기록이 비활성화되어 있어도 고정 재생 위치가 저장됩니다.
+• 목록 보기의 재생 위치로 인해 성능이 저하되는 문제 수정 #2517
+• [추출기] ReCaptchaActivity #2527, TeamNewPipe/NewPipeExtractor#186 수정
+• [Extractor] [YouTube] 결과에 재생목록이 있을 때 일반 검색 오류 수정 TeamNewPipe/NewPipeExtractor#185
+
+
+
+0.17.0의 변경 사항
+
+새로운
+재생 재개 #2288
+• 지난번에 중단한 스트림 재개
+다운로더 개선 사항 #2149
+• 저장소 액세스 프레임워크를 사용하여 외부 SD 카드에 다운로드 저장
+• 새로운 mp4 먹서
+• 선택적으로 다운로드를 시작하기 전에 다운로드 디렉터리를 변경합니다.
+• 종량제 네트워크를 존중합니다.
+
+
+향상
+• gema 문자열 #2295 제거됨
+• 활동 수명주기 동안 (자동)회전 변경 처리 #2444
+• 길게 누르기 메뉴를 일관성있게 만들기 #2368
+
+결정된
+• 선택한 자막 트랙 이름이 표시되지 않는 문제 수정 #2394
+• 앱 업데이트 확인 실패 시 충돌 방지(GitHub 버전) #2423
+• 다운로드가 99.9%에서 멈추는 현상 수정 #2440
+• 재생 대기열 메타데이터 업데이트 #2453
+• [SoundCloud] 재생 목록을 로드할 때 발생하는 충돌 수정 TeamNewPipe/NewPipeExtractor#170
+• [YouTube] 고정 기간은 TeamNewPipe/NewPipeExtractor#177로 파싱할 수 없습니다.
diff --git a/fastlane/metadata/android/ko/changelogs/770.txt b/fastlane/metadata/android/ko/changelogs/770.txt
new file mode 100644
index 000000000..3008815d8
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/770.txt
@@ -0,0 +1,4 @@
+0.17.2의 변경 사항
+
+고치다
+• 사용할 수 있는 동영상이 없는 문제 수정
diff --git a/fastlane/metadata/android/ko/changelogs/780.txt b/fastlane/metadata/android/ko/changelogs/780.txt
new file mode 100644
index 000000000..114b38dd5
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/780.txt
@@ -0,0 +1,12 @@
+0.17.3의 변경 사항
+
+향상
+• 재생 상태를 지우는 옵션 추가 #2550
+• 파일 선택기에 숨겨진 디렉터리 표시 #2591
+• `invidio.us` 인스턴스의 URL이 NewPipe #2488로 열리도록 지원합니다.
+• 'music.youtube.com' URL에 대한 지원 추가 TeamNewPipe/NewPipeExtractor#194
+
+결정된
+• [YouTube] 'java.lang.IllegalArgumentException #192 수정
+• [YouTube] 실시간 스트림이 작동하지 않는 문제 수정 TeamNewPipe/NewPipeExtractor#195
+• 스트림을 다운로드할 때 Android Pie의 성능 문제 수정 #2592
diff --git a/fastlane/metadata/android/ko/changelogs/790.txt b/fastlane/metadata/android/ko/changelogs/790.txt
new file mode 100644
index 000000000..c426443b2
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/790.txt
@@ -0,0 +1,14 @@
+향상
+• 시각 장애인의 접근성을 높이기 위해 더 많은 제목을 추가합니다. #2655
+• 다운로드 폴더 설정 언어를 보다 일관되고 덜 모호하게 만듭니다. #2637
+
+결정된
+• 블록의 마지막 바이트가 다운로드되었는지 확인 #2646
+• 비디오 세부 정보 조각 #2672의 스크롤 문제를 해결했습니다.
+• 이중 검색 지우기 상자 애니메이션을 하나로 제거 #2695
+• [SoundCloud] client_id 추출 수정 #2745
+
+개발
+• NewPipeExtractor에서 상속된 누락된 종속성을 NewPipe #2535에 추가합니다.
+• AndroidX #2685로 마이그레이션
+• ExoPlayer 2.10.6 #2697, #2736으로 업데이트
diff --git a/fastlane/metadata/android/ko/changelogs/800.txt b/fastlane/metadata/android/ko/changelogs/800.txt
new file mode 100644
index 000000000..04907891e
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/800.txt
@@ -0,0 +1,27 @@
+새로운
+• P2P 없이 PeerTube 지원(#2201)[베타]:
+  ◦ PeerTube 인스턴스에서 비디오 시청 및 다운로드
+  ◦ 전체 PeerTube 세계에 액세스하려면 설정에 인스턴스를 추가하세요.
+  ◦ 특정 인스턴스에 액세스할 때 Android 4.4 및 7.1에서 SSL 핸드셰이크에 문제가 있어 네트워크 오류가 발생할 수 있습니다.
+
+• 다운로더(#2679):
+  ◦ 다운로드 ETA 계산
+  ◦ opus(webm 파일)를 ogg로 다운로드
+  ◦ 오랜 일시 중지 후 다운로드를 재개하려면 만료된 다운로드 링크를 복구하세요.
+
+향상
+• KioskFragment가 선호하는 콘텐츠 국가의 변경 사항을 인식하고 모든 기본 탭의 성능을 향상시킵니다. #2742
+• 추출기 #2713에서 새로운 현지화 및 다운로더 구현을 사용합니다.
+• '기본 키오스크' 문자열을 번역 가능하게 만듭니다.
+• 검은색 테마용 검은색 탐색 모음 #2569
+
+결정된
+• 팝업플레이어 이동 중 다른 손가락을 놓으면 팝업플레이어가 이동되지 않는 버그 수정 #2772
+• 업로더가 없는 재생목록을 허용하고 이 문제 #2724, TeamNewPipe/NewPipeExtractor#219와 관련된 충돌을 해결합니다.
+• Android 4.4 기기(API 19/KitKat)에서 TLS1.1/1.2를 활성화하여 MediaCCC 및 일부 PeerTube 인스턴스와의 TLS 핸드셰이크 수정 #2792
+• [SoundCloud] client_id 추출 수정 TeamNewPipe/NewPipeExtractor#217
+• [SoundCloud] 오디오 스트림 추출 수정
+
+개발
+• ExoPlayer를 2.10.8 #2791, #2816으로 업데이트
+• Gradle을 3.5.1로 업데이트하고 Kotlin 지원 #2714를 추가합니다.
diff --git a/fastlane/metadata/android/ko/changelogs/810.txt b/fastlane/metadata/android/ko/changelogs/810.txt
new file mode 100644
index 000000000..38ab2c4b3
--- /dev/null
+++ b/fastlane/metadata/android/ko/changelogs/810.txt
@@ -0,0 +1,19 @@
+새로운
+• 백그라운드에서 재생할 때 잠금 화면에 동영상 미리보기 이미지 표시
+
+향상
+• 배경/팝업 버튼을 길게 누르면 대기열에 로컬 재생 목록 추가
+• 기본 페이지 탭을 스크롤 가능하게 만들고 탭이 하나만 있는 경우 숨깁니다.
+• 백그라운드 플레이어의 알림 썸네일 업데이트 양 제한
+• 빈 로컬 재생목록에 더미 썸네일 추가
+• *.webm 대신 *.opus 파일 확장자를 사용하고 다운로드 드롭다운에서 'WebM Opus' 대신 형식 라벨에 'opus'를 표시합니다.
+• '다운로드'에 다운로드한 파일이나 다운로드 기록을 삭제하는 버튼 추가
+• [YouTube] /c/shortened_url 채널 링크에 지원 추가
+
+결정된
+• NewPipe에 비디오를 공유하고 해당 스트림을 직접 다운로드할 때 여러 문제가 해결되었습니다.
+• 생성 스레드 외부에서 플레이어 액세스 수정
+• 검색결과 페이지 고정
+• [YouTube] Null 전환으로 인해 NPE가 발생하는 문제 수정
+• [YouTube] invidio.us URL을 열 때 댓글 보기 문제를 해결했습니다.
+• [SoundCloud] client_id 업데이트됨
diff --git a/fastlane/metadata/android/vi/changelogs/65.txt b/fastlane/metadata/android/vi/changelogs/65.txt
index 3bffa51b0..de47bd38b 100644
--- a/fastlane/metadata/android/vi/changelogs/65.txt
+++ b/fastlane/metadata/android/vi/changelogs/65.txt
@@ -1,9 +1,27 @@
 ##Cải thiện:
+### Cải tiến
 
-- Tắt hoạt ảnh icon burgermenu #1486
-- Cho phép hoàn tắc nội dung đã tải xuống #1472
-- Tùy chọn tải xuống ở menu chia sẻ #1498
-- Đã thêm tùy chọn chia sẻ vào menu nhẫn giữ #1454
-- Thu nhỏ bộ phát phương tiện chính khi thoát #1354
-- Sửa lỗi Cập nhật thư viện và cơ sở dữ liệu dự phòng #1510
-- Cập nhật trình phát ExoPlayer 2.8.2 #1392
+- Tắt hoạt ảnh biểu tượng burgermenu #1486
+- hoàn tác xóa các bản tải xuống # 1472
+- Tùy chọn tải xuống trong menu chia sẻ #1498
+- Đã thêm tùy chọn chia sẻ vào menu nhấn dài #1454
+- Thu nhỏ trình phát chính ở lối ra #1354
+- Cập nhật phiên bản thư viện và sửa lỗi sao lưu cơ sở dữ liệu #1510
+- Cập nhật ExoPlayer 2.8.2 #1392
+   - Làm lại hộp thoại kiểm soát tốc độ phát lại để hỗ trợ các kích cỡ bước khác nhau nhằm thay đổi tốc độ nhanh hơn.
+   - Đã thêm nút chuyển đổi để tua đi nhanh trong khi im lặng trong điều khiển tốc độ phát lại. Điều này sẽ hữu ích cho sách nói và một số thể loại âm nhạc nhất định, đồng thời có thể mang lại trải nghiệm liền mạch thực sự (và có thể ngắt một bài hát có nhiều khoảng lặng =\\).
+   - Độ phân giải nguồn phương tiện được tái cấu trúc để cho phép truyền siêu dữ liệu cùng với phương tiện bên trong trình phát, thay vì thực hiện thủ công. Bây giờ chúng tôi có một nguồn siêu dữ liệu duy nhất và có sẵn trực tiếp khi quá trình phát lại bắt đầu.
+   - Đã sửa lỗi siêu dữ liệu danh sách phát từ xa không cập nhật khi có siêu dữ liệu mới khi mở đoạn danh sách phát.
+   - Các bản sửa lỗi giao diện người dùng khác nhau: #1383, các điều khiển thông báo trình phát nền giờ đây luôn có màu trắng, dễ dàng tắt trình phát bật lên hơn thông qua thao tác ném
+- Sử dụng trình trích xuất mới với kiến trúc được tái cấu trúc cho nhiều dịch vụ
+
+### Sửa lỗi
+
+- Sửa lỗi #1440 Bố cục thông tin video bị hỏng #1491
+- Xem lịch sử sửa lỗi #1497
+   - #1495, bằng cách cập nhật siêu dữ liệu (hình thu nhỏ, tiêu đề và số lượng video) ngay khi người dùng truy cập danh sách phát.
+   - #1475, bằng cách đăng ký chế độ xem trong cơ sở dữ liệu khi người dùng bắt đầu phát video trên trình phát bên ngoài trên đoạn chi tiết.
+- Sửa lỗi hết thời gian chờ màn hình trong trường hợp ở chế độ bật lên. #1463 (Đã sửa #640)
+- Sửa lỗi trình phát video chính #1509
+   - [#1412] Đã sửa lỗi chế độ lặp lại khiến người chơi gặp NPE khi nhận được ý định mới trong khi hoạt động của người chơi ở chế độ nền.
+   - Đã sửa lỗi thu nhỏ trình phát thành cửa sổ bật lên không hủy trình phát khi không được cấp quyền bật lên.
diff --git a/fastlane/metadata/android/vi/changelogs/66.txt b/fastlane/metadata/android/vi/changelogs/66.txt
new file mode 100644
index 000000000..99b09fe05
--- /dev/null
+++ b/fastlane/metadata/android/vi/changelogs/66.txt
@@ -0,0 +1,33 @@
+# Nhật ký thay đổi của v0.13.7
+
+### Đã sửa
+- Khắc phục sự cố bộ lọc sắp xếp của v0.13.6
+
+# Nhật ký thay đổi của v0.13.6
+
+### Cải tiến
+
+- Tắt hoạt ảnh biểu tượng burgermenu #1486
+- hoàn tác xóa tải xuống #1472
+- Tùy chọn tải xuống trong menu chia sẻ #1498
+- Đã thêm tùy chọn chia sẻ vào menu nhấn dài #1454
+- Thu nhỏ trình phát chính ở lối ra #1354
+- Phiên bản thư viện cập nhật và bản sửa lỗi sao lưu cơ sở dữ liệu #1510
+- ExoPlayer 2.8.2 Cập nhật #1392
+- Làm lại hộp thoại kiểm soát tốc độ phát lại để hỗ trợ các kích cỡ bước khác nhau nhằm thay đổi tốc độ nhanh hơn.
+- Đã thêm nút chuyển đổi để tua đi nhanh trong khi im lặng trong điều khiển tốc độ phát lại. Điều này sẽ hữu ích cho sách nói và một số thể loại âm nhạc nhất định, đồng thời có thể mang lại trải nghiệm liền mạch thực sự (và có thể ngắt một bài hát có nhiều khoảng lặng =\\).
+- Độ phân giải nguồn phương tiện được tái cấu trúc để cho phép truyền siêu dữ liệu cùng với phương tiện nội bộ trong trình phát thay vì thực hiện thủ công. Bây giờ chúng tôi có một nguồn siêu dữ liệu duy nhất và có sẵn trực tiếp khi quá trình phát lại bắt đầu.
+- Đã sửa lỗi siêu dữ liệu danh sách phát từ xa không cập nhật khi có siêu dữ liệu mới khi mở đoạn danh sách phát.
+- Nhiều bản sửa lỗi giao diện người dùng khác nhau: #1383, các điều khiển thông báo trình phát nền giờ đây luôn có màu trắng, dễ dàng tắt trình phát cửa sổ bật lên thông qua thao tác ném
+- Sử dụng trình trích xuất mới với kiến trúc được tái cấu trúc cho nhiều dịch vụ
+
+### Sửa lỗi
+
+- Sửa lỗi #1440 Bố cục thông tin video bị hỏng #1491
+- Xem lịch sử sửa lỗi #1497
+- #1495, bằng cách cập nhật siêu dữ liệu (hình thu nhỏ, tiêu đề và số lượng video) ngay khi người dùng truy cập vào danh sách phát.
+- #1475, bằng cách đăng ký chế độ xem trong cơ sở dữ liệu khi người dùng bắt đầu video trên trình phát bên ngoài trên đoạn chi tiết.
+- Sửa lỗi màn hình hết thời gian chờ trong chế độ bật lên. #1463 (Đã sửa #640)
+- Sửa lỗi trình phát video chính #1509
+- [#1412] Đã sửa lỗi chế độ lặp lại khiến người chơi NPE khi nhận được ý định mới trong khi hoạt động của người chơi ở trong nền.
+- Đã sửa lỗi thu nhỏ trình phát thành cửa sổ bật lên không hủy trình phát khi không được cấp quyền cửa sổ bật lên.
diff --git a/fastlane/metadata/android/vi/changelogs/68.txt b/fastlane/metadata/android/vi/changelogs/68.txt
new file mode 100644
index 000000000..2247be255
--- /dev/null
+++ b/fastlane/metadata/android/vi/changelogs/68.txt
@@ -0,0 +1,31 @@
+# thay đổi của v0.14.1
+
+### Đã sửa
+- Đã sửa lỗi không giải mã được url video #1659
+- Sửa lỗi link mô tả không giải nén tốt #1657
+
+# thay đổi của v0.14.0
+
+### Mới
+- Thiết kế ngăn kéo mới #1461
+- Trang trước có thể tùy chỉnh mới #1461
+
+### Cải tiến
+- Điều khiển cử chỉ được làm lại #1604
+- Cách mới để đóng trình phát cửa sổ bật lên #1597
+
+### Đã sửa
+- Sửa lỗi khi không có số lượng đăng ký. Đóng #1649.
+- Hiển thị "Không có số lượng người đăng ký" trong những trường hợp đó
+- Khắc phục NPE khi danh sách phát YouTube trống
+- Sửa nhanh các ki-ốt trong SoundCloud
+- Tái cấu trúc và sửa lỗi #1623
+- Sửa kết quả tìm kiếm theo chu kỳ #1562
+- Sửa lỗi thanh Tìm kiếm không được bố trí tĩnh
+- Sửa lỗi video YT Premium không bị chặn đúng cách
+- Khắc phục Video đôi khi không tải (do phân tích cú pháp DASH)
+- Sửa các liên kết trong phần mô tả video
+- Hiển thị cảnh báo khi ai đó cố gắng tải xuống thẻ sdcard bên ngoài
+- sửa lỗi không hiển thị báo cáo kích hoạt ngoại lệ
+- hình thu nhỏ không hiển thị trong trình phát nền dành cho android 8.1 [xem tại đây](https://github.com/TeamNewPipe/NewPipe/issues/943)
+- Sửa lỗi đăng ký máy thu phát sóng. Đóng #1641.
diff --git a/fastlane/metadata/android/vi/changelogs/69.txt b/fastlane/metadata/android/vi/changelogs/69.txt
new file mode 100644
index 000000000..a6fb21da0
--- /dev/null
+++ b/fastlane/metadata/android/vi/changelogs/69.txt
@@ -0,0 +1,19 @@
+### Mới
+- Nhấn và giữ để xóa và chia sẻ trong đăng ký #1516
+- Giao diện người dùng máy tính bảng và bố cục danh sách lưới #1617
+
+### Cải tiến
+- lưu trữ và tải lại tỷ lệ khung hình được sử dụng lần cuối # 1748
+- Bật bố cục tuyến tính trong hoạt động Tải xuống với tên video đầy đủ #1771
+- Xóa và chia sẻ đăng ký trực tiếp từ trong tab đăng ký #1516
+- Việc xếp hàng hiện sẽ kích hoạt phát video nếu hàng đợi phát đã kết thúc #1783
+- Riêng cài đặt cho cử chỉ âm lượng và độ sáng #1644
+- Thêm hỗ trợ Bản địa hóa #1792
+
+### Sửa lỗi
+- Sửa lỗi phân tích thời gian cho . định dạng, vì vậy NewPipe có thể được sử dụng ở Phần Lan
+- Sửa số lượng đăng ký
+- Thêm quyền dịch vụ nền trước cho các thiết bị API 28+ #1830
+
+### Lỗi đã biết
+- Không thể lưu trạng thái phát lại trên Android P
diff --git a/fastlane/metadata/android/vi/changelogs/820.txt b/fastlane/metadata/android/vi/changelogs/820.txt
index f04843377..8554471a8 100644
--- a/fastlane/metadata/android/vi/changelogs/820.txt
+++ b/fastlane/metadata/android/vi/changelogs/820.txt
@@ -1 +1 @@
-Sửa regex trong hàm giải mã đang làm cho phần YouTube không xài được.
+Sửa regex trong hàm giải mã đang làm cho phần YouTube không dùng được.

From c915b6e68b4613d414ce97acc1e58eb2f9a91410 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Mon, 1 Apr 2024 14:16:51 +0200
Subject: [PATCH 131/141] Add changelog for v0.27.0 (997)

---
 .../metadata/android/en-US/changelogs/997.txt   | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/997.txt

diff --git a/fastlane/metadata/android/en-US/changelogs/997.txt b/fastlane/metadata/android/en-US/changelogs/997.txt
new file mode 100644
index 000000000..954324070
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/997.txt
@@ -0,0 +1,17 @@
+New
+• Add comment replies
+• Allow reordering playlists
+• Show playlist description and duration
+• Allow resetting settings
+
+Improved
+• [Android 13+] Restore custom notification actions
+• Request consent for update check
+• Allow notification play/pause while buffering
+• Reorder some settings
+
+Fixed
+• [YouTube] Fix comments not loading, plus other fixes and improvements
+• Solve vulnerability in settings import and switch to JSON
+• Various download fixes
+• Trim search text
\ No newline at end of file

From 7abf0f4886fefc922d0801d079716f48553ca31a Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Mon, 1 Apr 2024 14:23:04 +0200
Subject: [PATCH 132/141] Update NewPipeExtractor to YT comments fix PR

https://github.com/TeamNewPipe/NewPipeExtractor/pull/1163
---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 7a3b28661..72cab42de 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -198,7 +198,7 @@ dependencies {
     // name and the commit hash with the commit hash of the (pushed) commit you want to test
     // This works thanks to JitPack: https://jitpack.io/
     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
-    implementation 'com.github.Stypox:NewPipeExtractor:aaf3231fc75d7b4177549fec4aa7e672bfe84015'
+    implementation 'com.github.AudricV:NewPipeExtractor:2eca6d3ae62dde0d0d3933ba01031ec07a1a940e'
     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
 
 /** Checkstyle **/

From f704721b59200faf35ed358a9dbea7965fc66bb9 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Mon, 1 Apr 2024 14:23:48 +0200
Subject: [PATCH 133/141] Release v0.27.0 (997)

---
 app/build.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/build.gradle b/app/build.gradle
index 72cab42de..2c4ea383c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,8 +20,8 @@ android {
         resValue "string", "app_name", "NewPipe"
         minSdk 21
         targetSdk 33
-        versionCode 996
-        versionName "0.26.1"
+        versionCode 997
+        versionName "0.27.0"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 

From 13baaa31cd94831688df5c15838c7a3703e7306e Mon Sep 17 00:00:00 2001
From: bg1722 <bg1722>
Date: Sat, 6 Apr 2024 07:58:05 +0200
Subject: [PATCH 134/141] add an intuitive prefix for the duration of lists on
 UI, and avoid using the new prefix for single videos

---
 .../fragments/list/playlist/PlaylistFragment.java        | 2 +-
 .../newpipe/local/playlist/LocalPlaylistFragment.java    | 3 ++-
 .../main/java/org/schabi/newpipe/util/Localization.java  | 9 ++++++---
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index 998ea0624..9afb06344 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -506,7 +506,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
                 Localization.concatenateStrings(
                     Localization.localizeStreamCount(activity, streamCount),
                     Localization.getDurationString(playlistOverallDurationSeconds,
-                            isDurationComplete))
+                            isDurationComplete, true))
             );
         }
     }
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index e2d0f5986..d5ae431fa 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -837,7 +837,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
             headerBinding.playlistStreamCount.setText(
                     Localization.concatenateStrings(
                             Localization.localizeStreamCount(activity, streamCount),
-                            Localization.getDurationString(playlistOverallDurationSeconds))
+                            Localization.getDurationString(playlistOverallDurationSeconds,
+                                                            true, true))
             );
         }
     }
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 5d73d21f0..bc113e8f8 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -245,7 +245,7 @@ public final class Localization {
      * @return a formatted duration String or {@code 0:00} if the duration is zero.
      */
     public static String getDurationString(final long duration) {
-        return getDurationString(duration, true);
+        return getDurationString(duration, true, false);
     }
 
     /**
@@ -254,9 +254,11 @@ public final class Localization {
      * duration string.
      * @param duration the duration in seconds
      * @param isDurationComplete whether the given duration is complete or whether info is missing
+     * @param showDurationPrefix whether the duration-prefix shall be shown
      * @return a formatted duration String or {@code 0:00} if the duration is zero.
      */
-    public static String getDurationString(final long duration, final boolean isDurationComplete) {
+    public static String getDurationString(final long duration, final boolean isDurationComplete,
+                                           final boolean showDurationPrefix) {
         final String output;
 
         final long days = duration / (24 * 60 * 60L); /* greater than a day */
@@ -274,8 +276,9 @@ public final class Localization {
         } else {
             output = String.format(Locale.US, "%d:%02d", minutes, seconds);
         }
+        final String durationPrefix = showDurationPrefix ? "⏱ " : "";
         final String durationPostfix = isDurationComplete ? "" : "+";
-        return output + durationPostfix;
+        return durationPrefix + output + durationPostfix;
     }
 
     /**

From 0ba73b11c162ddcfdd70bfc3f0b74536b3ac3bd8 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sun, 7 Apr 2024 23:44:22 +0200
Subject: [PATCH 135/141] Update NewPipeExtractor

---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index 2c4ea383c..a4fde4ccc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -198,7 +198,7 @@ dependencies {
     // name and the commit hash with the commit hash of the (pushed) commit you want to test
     // This works thanks to JitPack: https://jitpack.io/
     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
-    implementation 'com.github.AudricV:NewPipeExtractor:2eca6d3ae62dde0d0d3933ba01031ec07a1a940e'
+    implementation 'com.github.AudricV:NewPipeExtractor:f4dcab16004e566f1969bc6f627facf198573fee'
     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
 
 /** Checkstyle **/

From 3738e309495ae7de404268b20a85b9ebe87cb4d7 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 9 Apr 2024 20:18:21 +0200
Subject: [PATCH 136/141] Fix NPE when avatarUrl is empty

---
 .../newpipe/database/subscription/SubscriptionEntity.java     | 4 +++-
 .../org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt   | 2 +-
 .../schabi/newpipe/local/subscription/SubscriptionManager.kt  | 4 +++-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
index a61a22a84..df5a3067a 100644
--- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
+++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java
@@ -1,6 +1,7 @@
 package org.schabi.newpipe.database.subscription;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
 import androidx.room.Ignore;
@@ -95,11 +96,12 @@ public class SubscriptionEntity {
         this.name = name;
     }
 
+    @Nullable
     public String getAvatarUrl() {
         return avatarUrl;
     }
 
-    public void setAvatarUrl(final String avatarUrl) {
+    public void setAvatarUrl(@Nullable final String avatarUrl) {
         this.avatarUrl = avatarUrl;
     }
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt
index 84cd8ed59..b44eec353 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt
@@ -18,7 +18,7 @@ data class FeedUpdateInfo(
     @NotificationMode
     val notificationMode: Int,
     val name: String,
-    val avatarUrl: String,
+    val avatarUrl: String?,
     val url: String,
     val serviceId: Int,
     // description and subscriberCount are null if the constructor info is from the fast feed method
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
index 488d8b3d2..474add4f4 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt
@@ -100,7 +100,9 @@ class SubscriptionManager(context: Context) {
         val subscriptionEntity = subscriptionTable.getSubscription(info.uid)
 
         subscriptionEntity.name = info.name
-        subscriptionEntity.avatarUrl = info.avatarUrl
+
+        // some services do not provide an avatar URL
+        info.avatarUrl?.let { subscriptionEntity.avatarUrl = it }
 
         // these two fields are null if the feed info was fetched using the fast feed method
         info.description?.let { subscriptionEntity.description = it }

From 67b41b970da9e6f039b5d877220ef2c557c75b3f Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Wed, 10 Apr 2024 10:52:47 +0200
Subject: [PATCH 137/141] Fix not saving comment replies state on config change

---
 .../fragments/list/comments/CommentRepliesFragment.java       | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
index a816b149f..304eaf55a 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
@@ -30,6 +30,7 @@ import org.schabi.newpipe.util.text.TextLinkifier;
 import java.util.Queue;
 import java.util.function.Supplier;
 
+import icepick.State;
 import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
@@ -38,7 +39,8 @@ public final class CommentRepliesFragment
 
     public static final String TAG = CommentRepliesFragment.class.getSimpleName();
 
-    private CommentsInfoItem commentsInfoItem; // the comment to show replies of
+    @State
+    CommentsInfoItem commentsInfoItem; // the comment to show replies of
     private final CompositeDisposable disposables = new CompositeDisposable();
 
 

From 00770fc63484b9946a53a1a800fe9fe9add3573b Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Sat, 20 Apr 2024 13:11:08 +0200
Subject: [PATCH 138/141] Update NewPipeExtractor

---
 app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/build.gradle b/app/build.gradle
index a4fde4ccc..1f3d1f759 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -198,7 +198,7 @@ dependencies {
     // name and the commit hash with the commit hash of the (pushed) commit you want to test
     // This works thanks to JitPack: https://jitpack.io/
     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
-    implementation 'com.github.AudricV:NewPipeExtractor:f4dcab16004e566f1969bc6f627facf198573fee'
+    implementation 'com.github.TeamNewPipe:NewPipeExtractor:fbe9e6223aceac8d6f6b352afaed4cb61aed1c79'
     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
 
 /** Checkstyle **/

From c3c39a7b24af5c3b062999103e90be3e56547850 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 23 Apr 2024 12:16:06 +0200
Subject: [PATCH 139/141] Fix free storage space check for all APIs

See https://stackoverflow.com/q/31171838
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html
---
 .../newpipe/download/DownloadDialog.java      | 25 +++---
 .../streams/io/StoredDirectoryHelper.java     | 77 +++++++++----------
 .../java/us/shandian/giga/util/Utility.java   | 14 ----
 3 files changed, 50 insertions(+), 66 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index bbdb46292..db2066b27 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -859,20 +859,19 @@ public class DownloadDialog extends DialogFragment
             return;
         }
 
-        // Check for free memory space (for api 24 and up)
-        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
-            final long freeSpace = mainStorage.getFreeMemory();
-            if (freeSpace <= size) {
-                Toast.makeText(context, getString(R.
-                        string.error_insufficient_storage), Toast.LENGTH_LONG).show();
-                // move the user to storage setting tab
-                final Intent storageSettingsIntent = new Intent(Settings.
-                        ACTION_INTERNAL_STORAGE_SETTINGS);
-                if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) {
-                    startActivity(storageSettingsIntent);
-                }
-                return;
+        // Check for free storage space
+        final long freeSpace = mainStorage.getFreeStorageSpace();
+        if (freeSpace <= size) {
+            Toast.makeText(context, getString(R.
+                    string.error_insufficient_storage), Toast.LENGTH_LONG).show();
+            // move the user to storage setting tab
+            final Intent storageSettingsIntent = new Intent(Settings.
+                    ACTION_INTERNAL_STORAGE_SETTINGS);
+            if (storageSettingsIntent.resolveActivity(context.getPackageManager())
+                    != null) {
+                startActivity(storageSettingsIntent);
             }
+            return;
         }
 
         // check for existing file with the same name
diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
index 0fe2e0408..8dd819293 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
@@ -1,24 +1,29 @@
 package org.schabi.newpipe.streams.io;
 
+import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
+import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
+import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.os.ParcelFileDescriptor;
 import android.provider.DocumentsContract;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStatVfs;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.documentfile.provider.DocumentFile;
 
 import org.schabi.newpipe.settings.NewPipeSettings;
 import org.schabi.newpipe.util.FilePickerActivityHelper;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.URI;
 import java.nio.file.Files;
@@ -27,16 +32,9 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
-import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
-import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
-
-import us.shandian.giga.util.Utility;
-
 public class StoredDirectoryHelper {
     private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
     public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -45,6 +43,7 @@ public class StoredDirectoryHelper {
     private Path ioTree;
     private DocumentFile docTree;
 
+    // will be `null` for non-SAF files, i.e. files that use `ioTree`
     private Context context;
 
     private final String tag;
@@ -176,41 +175,41 @@ public class StoredDirectoryHelper {
     }
 
     /**
-     * Get free memory of the storage partition (root of the directory).
-     * @return amount of free memory in the volume of current directory (bytes)
+     * Get free memory of the storage partition this file belongs to (root of the directory).
+     * See <a href="https://stackoverflow.com/q/31171838">StackOverflow</a> and
+     * <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html">
+     *     {@code statvfs()} and {@code fstatvfs()} docs</a>
+     *
+     * @return amount of free memory in the volume of current directory (bytes), or {@link
+     * Long#MAX_VALUE} if an error occurred
      */
-    @RequiresApi(api = Build.VERSION_CODES.N)  // Necessary for `getStorageVolume()`
-    public long getFreeMemory() {
-        final Uri uri = getUri();
-        final StorageManager storageManager = (StorageManager) context.
-                getSystemService(Context.STORAGE_SERVICE);
-        final List<StorageVolume> volumes = storageManager.getStorageVolumes();
+    public long getFreeStorageSpace() {
+        try {
+            final StructStatVfs stat;
 
-        final String docId = DocumentsContract.getDocumentId(uri);
-        final String[] split = docId.split(":");
-        if (split.length > 0) {
-            final String volumeId = split[0];
+            if (ioTree != null) {
+                // non-SAF file, use statvfs with the path directly (also, `context` would be null
+                // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
+                stat = Os.statvfs(ioTree.toString());
 
-            for (final StorageVolume volume : volumes) {
-                // if the volume is an internal system volume
-                if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) {
-                    return Utility.getSystemFreeMemory();
-                }
-
-                // if the volume is a removable volume (normally an SD card)
-                if (volume.isRemovable() && !volume.isPrimary()) {
-                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
-                        try {
-                            final String sdCardUUID = volume.getUuid();
-                            return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID));
-                        } catch (final Exception e) {
-                            // do nothing
-                        }
+            } else {
+                // SAF file, we can't get a path directly, so obtain a file descriptor first
+                // and then use fstatvfs with the file descriptor
+                try (ParcelFileDescriptor parcelFileDescriptor =
+                             context.getContentResolver().openFileDescriptor(getUri(), "r")) {
+                    if (parcelFileDescriptor == null) {
+                        return Long.MAX_VALUE;
                     }
+                    final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+                    stat = Os.fstatvfs(fileDescriptor);
                 }
             }
+
+            // this is the same formula used inside the FsStat class
+            return stat.f_bavail * stat.f_frsize;
+        } catch (final IOException | ErrnoException e) {
+            return Long.MAX_VALUE;
         }
-        return Long.MAX_VALUE;
     }
 
     /**
diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java
index c75269757..86a08c57f 100644
--- a/app/src/main/java/us/shandian/giga/util/Utility.java
+++ b/app/src/main/java/us/shandian/giga/util/Utility.java
@@ -40,20 +40,6 @@ public class Utility {
         UNKNOWN
     }
 
-    /**
-     * Get amount of free system's memory.
-     * @return free memory (bytes)
-     */
-    public static long getSystemFreeMemory() {
-        try {
-            final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
-            return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
-        } catch (final Exception e) {
-            // do nothing
-        }
-        return -1;
-    }
-
     public static String formatBytes(long bytes) {
         Locale locale = Locale.getDefault();
         if (bytes < 1024) {

From caa3812e13d888f31b23487505462eea85f12e96 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 23 Apr 2024 18:05:31 +0200
Subject: [PATCH 140/141] Ignore all errors when getting free storage space

It's not a critical check that needs to be perfomed, so in case something does not work on some device/version, let's just ignore the error.
---
 .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
index 8dd819293..3f6bf37ea 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
@@ -11,7 +11,6 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.provider.DocumentsContract;
-import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructStatVfs;
 import android.util.Log;
@@ -207,7 +206,9 @@ public class StoredDirectoryHelper {
 
             // this is the same formula used inside the FsStat class
             return stat.f_bavail * stat.f_frsize;
-        } catch (final IOException | ErrnoException e) {
+        } catch (final Throwable e) {
+            // ignore any error
+            Log.e(TAG, "Could not get free storage space", e);
             return Long.MAX_VALUE;
         }
     }

From b8daf16b920e90ed98d3a06b4c6ddf30a21171f4 Mon Sep 17 00:00:00 2001
From: Stypox <stypox@pm.me>
Date: Tue, 23 Apr 2024 18:39:56 +0200
Subject: [PATCH 141/141] Update
 app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
---
 .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
index 3f6bf37ea..bb47a4b91 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
@@ -42,7 +42,10 @@ public class StoredDirectoryHelper {
     private Path ioTree;
     private DocumentFile docTree;
 
-    // will be `null` for non-SAF files, i.e. files that use `ioTree`
+    /**
+     * Context is `null` for non-SAF files, i.e. files that use `ioTree`.
+     */
+    @Nullable
     private Context context;
 
     private final String tag;