mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-07-03 08:08:48 +00:00
Compare commits
14 Commits
9a351f45a5
...
165c6cba72
Author | SHA1 | Date | |
---|---|---|---|
|
165c6cba72 | ||
|
ace7923e2f | ||
|
dac292181d | ||
|
99f22756e1 | ||
|
72ce9e3c75 | ||
|
4affc948fe | ||
|
5887438dde | ||
|
8ecd6e6b98 | ||
|
19c56f80d5 | ||
|
e6f01a2d19 | ||
|
465fc9f2a8 | ||
|
5b445fd877 | ||
|
b273c4772e | ||
|
db7a90fad0 |
|
@ -113,7 +113,7 @@ android {
|
||||||
ext {
|
ext {
|
||||||
checkstyleVersion = '10.12.1'
|
checkstyleVersion = '10.12.1'
|
||||||
|
|
||||||
androidxLifecycleVersion = '2.6.2'
|
androidxLifecycleVersion = '2.8.2'
|
||||||
androidxRoomVersion = '2.6.1'
|
androidxRoomVersion = '2.6.1'
|
||||||
androidxWorkVersion = '2.8.1'
|
androidxWorkVersion = '2.8.1'
|
||||||
|
|
||||||
|
@ -222,12 +222,14 @@ dependencies {
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-service:${androidxLifecycleVersion}"
|
||||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
|
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
|
||||||
implementation 'androidx.media:media:1.7.0'
|
implementation 'androidx.media:media:1.7.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||||
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
||||||
|
implementation "androidx.room:room-ktx:${androidxRoomVersion}"
|
||||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
||||||
|
@ -267,8 +269,7 @@ dependencies {
|
||||||
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
|
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
//noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
|
implementation 'io.coil-kt:coil:2.6.0'
|
||||||
implementation "com.squareup.picasso:picasso:2.8"
|
|
||||||
|
|
||||||
// Markdown library for Android
|
// Markdown library for Android
|
||||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||||
|
|
|
@ -129,7 +129,7 @@ class DatabaseMigrationTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
val migratedDatabaseV3 = getMigratedDatabase()
|
val migratedDatabaseV3 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV3.streamDAO().getAll().blockingFirst()
|
||||||
|
|
||||||
// Only expect 2, the one with the null url will be ignored
|
// Only expect 2, the one with the null url will be ignored
|
||||||
assertEquals(2, listFromDB.size)
|
assertEquals(2, listFromDB.size)
|
||||||
|
@ -217,7 +217,7 @@ class DatabaseMigrationTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
val migratedDatabaseV8 = getMigratedDatabase()
|
val migratedDatabaseV8 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV8.searchHistoryDAO().getAll().blockingFirst()
|
||||||
|
|
||||||
assertEquals(2, listFromDB.size)
|
assertEquals(2, listFromDB.size)
|
||||||
assertEquals("abc", listFromDB[0].search)
|
assertEquals("abc", listFromDB[0].search)
|
||||||
|
@ -283,8 +283,8 @@ class DatabaseMigrationTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
val migratedDatabaseV9 = getMigratedDatabase()
|
val migratedDatabaseV9 = getMigratedDatabase()
|
||||||
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
var localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
|
||||||
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
|
||||||
|
|
||||||
assertEquals(1, localListFromDB.size)
|
assertEquals(1, localListFromDB.size)
|
||||||
assertEquals(localUid2, localListFromDB[0].uid)
|
assertEquals(localUid2, localListFromDB[0].uid)
|
||||||
|
@ -303,8 +303,8 @@ class DatabaseMigrationTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
|
||||||
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
|
||||||
assertEquals(2, localListFromDB.size)
|
assertEquals(2, localListFromDB.size)
|
||||||
assertEquals(localUid3, localListFromDB[1].uid)
|
assertEquals(localUid3, localListFromDB[1].uid)
|
||||||
assertEquals(-1, localListFromDB[1].displayIndex)
|
assertEquals(-1, localListFromDB[1].displayIndex)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.database
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import io.reactivex.rxjava3.core.Single
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
|
@ -22,7 +22,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import kotlin.streams.toList
|
|
||||||
|
|
||||||
class FeedDAOTest {
|
class FeedDAOTest {
|
||||||
private lateinit var db: AppDatabase
|
private lateinit var db: AppDatabase
|
||||||
|
@ -94,17 +93,13 @@ class FeedDAOTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUnlinkDelete(time: String) {
|
private fun setupUnlinkDelete(time: String) = runBlocking {
|
||||||
clearAndFillTables()
|
clearAndFillTables()
|
||||||
Single.fromCallable {
|
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
|
||||||
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
|
streamDAO.deleteOrphans()
|
||||||
}.blockingSubscribe()
|
|
||||||
Single.fromCallable {
|
|
||||||
streamDAO.deleteOrphans()
|
|
||||||
}.blockingSubscribe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearAndFillTables() {
|
private suspend fun clearAndFillTables() {
|
||||||
db.clearAllTables()
|
db.clearAllTables()
|
||||||
streamDAO.insertAll(allStreams)
|
streamDAO.insertAll(allStreams)
|
||||||
subscriptionDAO.insertAll(
|
subscriptionDAO.insertAll(
|
||||||
|
|
|
@ -41,7 +41,7 @@ class HistoryRecordManagerTest {
|
||||||
// For some reason the Flowable returned by getAll() never completes, so we can't assert
|
// For some reason the Flowable returned by getAll() never completes, so we can't assert
|
||||||
// that the number of Lists it returns is exactly 1, we can only check if the first List is
|
// that the number of Lists it returns is exactly 1, we can only check if the first List is
|
||||||
// correct. Why on earth has a Flowable been used instead of a Single for getAll()?!?
|
// correct. Why on earth has a Flowable been used instead of a Single for getAll()?!?
|
||||||
val entities = database.searchHistoryDAO().all.blockingFirst()
|
val entities = database.searchHistoryDAO().getAll().blockingFirst()
|
||||||
assertThat(entities).hasSize(1)
|
assertThat(entities).hasSize(1)
|
||||||
assertThat(entities[0].id).isEqualTo(1)
|
assertThat(entities[0].id).isEqualTo(1)
|
||||||
assertThat(entities[0].serviceId).isEqualTo(0)
|
assertThat(entities[0].serviceId).isEqualTo(0)
|
||||||
|
@ -59,25 +59,25 @@ class HistoryRecordManagerTest {
|
||||||
|
|
||||||
// make sure all 4 were inserted
|
// make sure all 4 were inserted
|
||||||
database.searchHistoryDAO().insertAll(entries)
|
database.searchHistoryDAO().insertAll(entries)
|
||||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
|
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries)
|
||||||
|
|
||||||
// try to delete only "A" entries, "B" entries should be untouched
|
// try to delete only "A" entries, "B" entries should be untouched
|
||||||
manager.deleteSearchHistory("A").test().await().assertValue(2)
|
manager.deleteSearchHistory("A").test().await().assertValue(2)
|
||||||
val entities = database.searchHistoryDAO().all.blockingFirst()
|
val entities = database.searchHistoryDAO().getAll().blockingFirst()
|
||||||
assertThat(entities).hasSize(2)
|
assertThat(entities).hasSize(2)
|
||||||
assertThat(entities).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
assertThat(entities).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
||||||
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
||||||
|
|
||||||
// assert that nothing happens if we delete a search query that does exist in the db
|
// assert that nothing happens if we delete a search query that does exist in the db
|
||||||
manager.deleteSearchHistory("A").test().await().assertValue(0)
|
manager.deleteSearchHistory("A").test().await().assertValue(0)
|
||||||
val entities2 = database.searchHistoryDAO().all.blockingFirst()
|
val entities2 = database.searchHistoryDAO().getAll().blockingFirst()
|
||||||
assertThat(entities2).hasSize(2)
|
assertThat(entities2).hasSize(2)
|
||||||
assertThat(entities2).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
assertThat(entities2).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
||||||
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
||||||
|
|
||||||
// delete all remaining entries
|
// delete all remaining entries
|
||||||
manager.deleteSearchHistory("B").test().await().assertValue(2)
|
manager.deleteSearchHistory("B").test().await().assertValue(2)
|
||||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
|
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -90,11 +90,11 @@ class HistoryRecordManagerTest {
|
||||||
|
|
||||||
// make sure all 3 were inserted
|
// make sure all 3 were inserted
|
||||||
database.searchHistoryDAO().insertAll(entries)
|
database.searchHistoryDAO().insertAll(entries)
|
||||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
|
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries)
|
||||||
|
|
||||||
// should remove everything
|
// should remove everything
|
||||||
manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size)
|
manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size)
|
||||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
|
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertShuffledRelatedSearches(relatedSearches: Collection<SearchHistoryEntry>) {
|
private fun insertShuffledRelatedSearches(relatedSearches: Collection<SearchHistoryEntry>) {
|
||||||
|
@ -107,7 +107,7 @@ class HistoryRecordManagerTest {
|
||||||
// make sure all entries were inserted
|
// make sure all entries were inserted
|
||||||
assertEquals(
|
assertEquals(
|
||||||
relatedSearches.size,
|
relatedSearches.size,
|
||||||
database.searchHistoryDAO().all.blockingFirst().size
|
database.searchHistoryDAO().getAll().blockingFirst().size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,6 @@ class LocalPlaylistManagerTest {
|
||||||
val result = manager.createPlaylist("name", listOf(stream, upserted))
|
val result = manager.createPlaylist("name", listOf(stream, upserted))
|
||||||
|
|
||||||
result.test().await().assertComplete()
|
result.test().await().assertComplete()
|
||||||
database.streamDAO().all.test().awaitCount(1).assertValue(listOf(stream, upserted))
|
database.streamDAO().getAll().test().awaitCount(1).assertValue(listOf(stream, upserted))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -8,6 +9,7 @@ import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.NotificationChannelCompat;
|
import androidx.core.app.NotificationChannelCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix;
|
import com.jakewharton.processphoenix.ProcessPhoenix;
|
||||||
|
@ -20,18 +22,23 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import coil.ImageLoader;
|
||||||
|
import coil.ImageLoaderFactory;
|
||||||
|
import coil.disk.DiskCache;
|
||||||
|
import coil.memory.MemoryCache;
|
||||||
|
import coil.util.DebugLogger;
|
||||||
import io.reactivex.rxjava3.exceptions.CompositeException;
|
import io.reactivex.rxjava3.exceptions.CompositeException;
|
||||||
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
|
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
|
||||||
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
|
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
|
||||||
|
@ -57,7 +64,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application implements ImageLoaderFactory {
|
||||||
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
|
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
|
||||||
private static final String TAG = App.class.toString();
|
private static final String TAG = App.class.toString();
|
||||||
|
|
||||||
|
@ -108,20 +115,33 @@ public class App extends Application {
|
||||||
|
|
||||||
// Initialize image loader
|
// Initialize image loader
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
PicassoHelper.init(this);
|
|
||||||
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this,
|
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this,
|
||||||
prefs.getString(getString(R.string.image_quality_key),
|
prefs.getString(getString(R.string.image_quality_key),
|
||||||
getString(R.string.image_quality_default))));
|
getString(R.string.image_quality_default))));
|
||||||
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
|
|
||||||
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
|
|
||||||
|
|
||||||
configureRxJavaErrorHandler();
|
configureRxJavaErrorHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public ImageLoader newImageLoader() {
|
||||||
super.onTerminate();
|
final var isLowRamDevice = ContextCompat.getSystemService(this, ActivityManager.class)
|
||||||
PicassoHelper.terminate();
|
.isLowRamDevice();
|
||||||
|
final var builder = new ImageLoader.Builder(this)
|
||||||
|
.memoryCache(() -> new MemoryCache.Builder(this)
|
||||||
|
.maxSizeBytes(10 * 1024 * 1024)
|
||||||
|
.build())
|
||||||
|
.diskCache(() -> new DiskCache.Builder()
|
||||||
|
.directory(new File(getExternalCacheDir(), "coil"))
|
||||||
|
.maxSizeBytes(50 * 1024 * 1024)
|
||||||
|
.build())
|
||||||
|
.allowRgb565(isLowRamDevice);
|
||||||
|
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
builder.logger(new DebugLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Downloader getDownloader() {
|
protected Downloader getDownloader() {
|
||||||
|
|
|
@ -167,8 +167,8 @@ class AboutActivity : AppCompatActivity() {
|
||||||
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
|
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
|
||||||
),
|
),
|
||||||
SoftwareComponent(
|
SoftwareComponent(
|
||||||
"Picasso", "2013", "Square, Inc.",
|
"Coil", "2023", "Coil Contributors",
|
||||||
"https://square.github.io/picasso/", StandardLicenses.APACHE2
|
"https://coil-kt.github.io/coil/", StandardLicenses.APACHE2
|
||||||
),
|
),
|
||||||
SoftwareComponent(
|
SoftwareComponent(
|
||||||
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
package org.schabi.newpipe.database;
|
|
||||||
|
|
||||||
import androidx.room.Dao;
|
|
||||||
import androidx.room.Delete;
|
|
||||||
import androidx.room.Insert;
|
|
||||||
import androidx.room.Update;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
public interface BasicDAO<Entity> {
|
|
||||||
/* Inserts */
|
|
||||||
@Insert
|
|
||||||
long insert(Entity entity);
|
|
||||||
|
|
||||||
@Insert
|
|
||||||
List<Long> insertAll(Collection<Entity> entities);
|
|
||||||
|
|
||||||
/* Searches */
|
|
||||||
Flowable<List<Entity>> getAll();
|
|
||||||
|
|
||||||
Flowable<List<Entity>> listByService(int serviceId);
|
|
||||||
|
|
||||||
/* Deletes */
|
|
||||||
@Delete
|
|
||||||
void delete(Entity entity);
|
|
||||||
|
|
||||||
int deleteAll();
|
|
||||||
|
|
||||||
/* Updates */
|
|
||||||
@Update
|
|
||||||
int update(Entity entity);
|
|
||||||
|
|
||||||
@Update
|
|
||||||
void update(Collection<Entity> entities);
|
|
||||||
}
|
|
32
app/src/main/java/org/schabi/newpipe/database/BasicDAO.kt
Normal file
32
app/src/main/java/org/schabi/newpipe/database/BasicDAO.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package org.schabi.newpipe.database
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Update
|
||||||
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface BasicDAO<Entity> {
|
||||||
|
/* Inserts */
|
||||||
|
@Insert
|
||||||
|
fun insert(entity: Entity): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertAll(entities: Collection<Entity>): List<Long>
|
||||||
|
|
||||||
|
/* Searches */
|
||||||
|
fun getAll(): Flowable<List<Entity>>
|
||||||
|
|
||||||
|
fun listByService(serviceId: Int): Flowable<List<Entity>>
|
||||||
|
|
||||||
|
/* Deletes */
|
||||||
|
@Delete
|
||||||
|
fun delete(entity: Entity)
|
||||||
|
|
||||||
|
fun deleteAll(): Int
|
||||||
|
|
||||||
|
/* Updates */
|
||||||
|
@Update
|
||||||
|
fun update(entity: Entity): Int
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.core.Maybe
|
import io.reactivex.rxjava3.core.Maybe
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||||
|
@ -18,9 +19,9 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class FeedDAO {
|
interface FeedDAO {
|
||||||
@Query("DELETE FROM feed")
|
@Query("DELETE FROM feed")
|
||||||
abstract fun deleteAll(): Int
|
suspend fun deleteAll(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param groupId the group id to get feed streams of; use
|
* @param groupId the group id to get feed streams of; use
|
||||||
|
@ -86,7 +87,7 @@ abstract class FeedDAO {
|
||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getStreams(
|
fun getStreams(
|
||||||
groupId: Long,
|
groupId: Long,
|
||||||
includePlayed: Boolean,
|
includePlayed: Boolean,
|
||||||
includePartiallyPlayed: Boolean,
|
includePartiallyPlayed: Boolean,
|
||||||
|
@ -119,7 +120,7 @@ abstract class FeedDAO {
|
||||||
AND s.upload_date <> max_upload_date))
|
AND s.upload_date <> max_upload_date))
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
|
suspend fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -137,22 +138,19 @@ abstract class FeedDAO {
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun unlinkOldLivestreams(subscriptionId: Long)
|
suspend fun unlinkOldLivestreams(subscriptionId: Long)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
abstract fun insert(feedEntity: FeedEntity)
|
suspend fun insertAll(entities: List<FeedEntity>): List<Long>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
abstract fun insertAll(entities: List<FeedEntity>): List<Long>
|
suspend fun insertLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity): Long
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
internal abstract fun insertLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity): Long
|
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.IGNORE)
|
@Update(onConflict = OnConflictStrategy.IGNORE)
|
||||||
internal abstract fun updateLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity)
|
suspend fun updateLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open fun setLastUpdatedForSubscription(lastUpdatedEntity: FeedLastUpdatedEntity) {
|
suspend fun setLastUpdatedForSubscription(lastUpdatedEntity: FeedLastUpdatedEntity) {
|
||||||
val id = insertLastUpdated(lastUpdatedEntity)
|
val id = insertLastUpdated(lastUpdatedEntity)
|
||||||
|
|
||||||
if (id == -1L) {
|
if (id == -1L) {
|
||||||
|
@ -168,13 +166,13 @@ abstract class FeedDAO {
|
||||||
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
|
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
|
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
|
||||||
|
|
||||||
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
|
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
|
||||||
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
|
fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
|
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
|
||||||
abstract fun notLoadedCount(): Flowable<Long>
|
fun notLoadedCount(): Flowable<Long>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +187,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL
|
WHERE lu.last_updated IS NULL
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
|
fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -201,7 +199,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
|
fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flow<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -216,7 +214,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
|
fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flow<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -230,8 +228,8 @@ abstract class FeedDAO {
|
||||||
AND s.notification_mode = :notificationMode
|
AND s.notification_mode = :notificationMode
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getOutdatedWithNotificationMode(
|
fun getOutdatedWithNotificationMode(
|
||||||
outdatedThreshold: OffsetDateTime,
|
outdatedThreshold: OffsetDateTime,
|
||||||
@NotificationMode notificationMode: Int
|
@NotificationMode notificationMode: Int
|
||||||
): Flowable<List<SubscriptionEntity>>
|
): Flow<List<SubscriptionEntity>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
package org.schabi.newpipe.database.history.dao;
|
package org.schabi.newpipe.database.history.dao;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
|
||||||
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;
|
||||||
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
|
||||||
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID;
|
||||||
|
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
|
@ -10,12 +16,6 @@ import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
|
|
||||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;
|
|
||||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
|
|
||||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID;
|
|
||||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME;
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.schabi.newpipe.database.playlist.dao;
|
package org.schabi.newpipe.database.playlist.dao;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.room.Transaction;
|
import androidx.room.Transaction;
|
||||||
|
@ -11,9 +14,6 @@ import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
|
public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
package org.schabi.newpipe.database.playlist.dao;
|
package org.schabi.newpipe.database.playlist.dao;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.room.Transaction;
|
import androidx.room.Transaction;
|
||||||
|
@ -11,12 +17,6 @@ import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
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;
|
|
||||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
|
public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import io.reactivex.rxjava3.core.Completable
|
import io.reactivex.rxjava3.core.Completable
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.schabi.newpipe.database.BasicDAO
|
import org.schabi.newpipe.database.BasicDAO
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
||||||
|
@ -16,30 +17,27 @@ import org.schabi.newpipe.util.StreamTypeUtil
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
interface StreamDAO : BasicDAO<StreamEntity> {
|
||||||
@Query("SELECT * FROM streams")
|
@Query("SELECT * FROM streams")
|
||||||
abstract override fun getAll(): Flowable<List<StreamEntity>>
|
override fun getAll(): Flowable<List<StreamEntity>>
|
||||||
|
|
||||||
@Query("DELETE FROM streams")
|
@Query("DELETE FROM streams")
|
||||||
abstract override fun deleteAll(): Int
|
override fun deleteAll(): Int
|
||||||
|
|
||||||
@Query("SELECT * FROM streams WHERE service_id = :serviceId")
|
@Query("SELECT * FROM streams WHERE service_id = :serviceId")
|
||||||
abstract override fun listByService(serviceId: Int): Flowable<List<StreamEntity>>
|
override fun listByService(serviceId: Int): Flowable<List<StreamEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM streams WHERE url = :url AND service_id = :serviceId")
|
@Query("SELECT * FROM streams WHERE url = :url AND service_id = :serviceId")
|
||||||
abstract fun getStream(serviceId: Long, url: String): Flowable<List<StreamEntity>>
|
fun getStream(serviceId: Long, url: String): Flowable<List<StreamEntity>>
|
||||||
|
|
||||||
@Query("UPDATE streams SET uploader_url = :uploaderUrl WHERE url = :url AND service_id = :serviceId")
|
@Query("UPDATE streams SET uploader_url = :uploaderUrl WHERE url = :url AND service_id = :serviceId")
|
||||||
abstract fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable
|
fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
internal abstract fun silentInsertInternal(stream: StreamEntity): Long
|
fun silentInsertInternal(stream: StreamEntity): Long
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
internal abstract fun silentInsertAllInternal(streams: List<StreamEntity>): List<Long>
|
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) != 0 FROM streams WHERE url = :url AND service_id = :serviceId")
|
@Query("SELECT COUNT(*) != 0 FROM streams WHERE url = :url AND service_id = :serviceId")
|
||||||
internal abstract fun exists(serviceId: Int, url: String): Boolean
|
suspend fun exists(serviceId: Int, url: String): Boolean
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -47,10 +45,10 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
FROM streams WHERE url = :url AND service_id = :serviceId
|
FROM streams WHERE url = :url AND service_id = :serviceId
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
|
fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open fun upsert(newerStream: StreamEntity): Long {
|
fun upsert(newerStream: StreamEntity): Long {
|
||||||
val uid = silentInsertInternal(newerStream)
|
val uid = silentInsertInternal(newerStream)
|
||||||
|
|
||||||
if (uid != -1L) {
|
if (uid != -1L) {
|
||||||
|
@ -65,24 +63,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open fun upsertAll(streams: List<StreamEntity>): List<Long> {
|
fun upsertAll(streams: List<StreamEntity>): List<Long> {
|
||||||
val insertUidList = silentInsertAllInternal(streams)
|
return streams.map { upsert(it) }
|
||||||
|
|
||||||
val streamIds = ArrayList<Long>(streams.size)
|
|
||||||
for ((index, uid) in insertUidList.withIndex()) {
|
|
||||||
val newerStream = streams[index]
|
|
||||||
if (uid != -1L) {
|
|
||||||
streamIds.add(uid)
|
|
||||||
newerStream.uid = uid
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
compareAndUpdateStream(newerStream)
|
|
||||||
streamIds.add(newerStream.uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
update(streams)
|
|
||||||
return streamIds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compareAndUpdateStream(newerStream: StreamEntity) {
|
private fun compareAndUpdateStream(newerStream: StreamEntity) {
|
||||||
|
@ -91,7 +73,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
newerStream.uid = existentMinimalStream.uid
|
newerStream.uid = existentMinimalStream.uid
|
||||||
|
|
||||||
if (!StreamTypeUtil.isLiveStream(newerStream.streamType)) {
|
if (!StreamTypeUtil.isLiveStream(newerStream.streamType)) {
|
||||||
|
|
||||||
// Use the existent upload date if the newer stream does not have a better precision
|
// Use the existent upload date if the newer stream does not have a better precision
|
||||||
// (i.e. is an approximation). This is done to prevent unnecessary changes.
|
// (i.e. is an approximation). This is done to prevent unnecessary changes.
|
||||||
val hasBetterPrecision =
|
val hasBetterPrecision =
|
||||||
|
@ -122,12 +103,16 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
WHERE f.stream_id = streams.uid)
|
WHERE f.stream_id = streams.uid)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun deleteOrphans(): Int
|
suspend fun deleteOrphans(): Int
|
||||||
|
|
||||||
|
fun deleteOrphansBlocking() = runBlocking {
|
||||||
|
deleteOrphans()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal entry class used when comparing/updating an existent stream.
|
* Minimal entry class used when comparing/updating an existent stream.
|
||||||
*/
|
*/
|
||||||
internal data class StreamCompareFeed(
|
data class StreamCompareFeed(
|
||||||
@ColumnInfo(name = STREAM_ID)
|
@ColumnInfo(name = STREAM_ID)
|
||||||
var uid: Long = 0,
|
var uid: Long = 0,
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.schabi.newpipe.database.stream.dao;
|
package org.schabi.newpipe.database.stream.dao;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
|
||||||
|
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
import androidx.room.OnConflictStrategy;
|
import androidx.room.OnConflictStrategy;
|
||||||
|
@ -13,9 +16,6 @@ import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
|
|
||||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface StreamStateDAO extends BasicDAO<StreamStateEntity> {
|
public interface StreamStateDAO extends BasicDAO<StreamStateEntity> {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,20 +6,25 @@ import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.core.Maybe
|
import io.reactivex.rxjava3.core.Maybe
|
||||||
import org.schabi.newpipe.database.BasicDAO
|
import org.schabi.newpipe.database.BasicDAO
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
interface SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
|
// TODO: Replace with the standard update method when migrating to suspend functions
|
||||||
|
@Update
|
||||||
|
suspend fun updateSubscription(entity: SubscriptionEntity)
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM subscriptions")
|
@Query("SELECT COUNT(*) FROM subscriptions")
|
||||||
abstract fun rowCount(): Flowable<Long>
|
fun rowCount(): Flowable<Long>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions WHERE service_id = :serviceId")
|
@Query("SELECT * FROM subscriptions WHERE service_id = :serviceId")
|
||||||
abstract override fun listByService(serviceId: Int): Flowable<List<SubscriptionEntity>>
|
override fun listByService(serviceId: Int): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -30,7 +35,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
ORDER BY name COLLATE NOCASE ASC
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@RewriteQueriesToDropUnusedColumns
|
@RewriteQueriesToDropUnusedColumns
|
||||||
@Query(
|
@Query(
|
||||||
|
@ -45,7 +50,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
ORDER BY name COLLATE NOCASE ASC
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getSubscriptionsOnlyUngrouped(
|
fun getSubscriptionsOnlyUngrouped(
|
||||||
currentGroupId: Long
|
currentGroupId: Long
|
||||||
): Flowable<List<SubscriptionEntity>>
|
): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
|
@ -63,34 +68,34 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
ORDER BY name COLLATE NOCASE ASC
|
ORDER BY name COLLATE NOCASE ASC
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
fun getSubscriptionsOnlyUngroupedFiltered(
|
||||||
currentGroupId: Long,
|
currentGroupId: Long,
|
||||||
filter: String
|
filter: String
|
||||||
): Flowable<List<SubscriptionEntity>>
|
): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||||
abstract fun getSubscription(serviceId: Int, url: String): Maybe<SubscriptionEntity>
|
fun getSubscription(serviceId: Int, url: String): Maybe<SubscriptionEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM subscriptions WHERE uid = :subscriptionId")
|
@Query("SELECT * FROM subscriptions WHERE uid = :subscriptionId")
|
||||||
abstract fun getSubscription(subscriptionId: Long): SubscriptionEntity
|
suspend fun getSubscription(subscriptionId: Long): SubscriptionEntity
|
||||||
|
|
||||||
@Query("DELETE FROM subscriptions")
|
@Query("DELETE FROM subscriptions")
|
||||||
abstract override fun deleteAll(): Int
|
override fun deleteAll(): Int
|
||||||
|
|
||||||
@Query("DELETE FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
@Query("DELETE FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||||
abstract fun deleteSubscription(serviceId: Int, url: String): Int
|
fun deleteSubscription(serviceId: Int, url: String): Int
|
||||||
|
|
||||||
@Query("SELECT uid FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
@Query("SELECT uid FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||||
internal abstract fun getSubscriptionIdInternal(serviceId: Int, url: String): Long?
|
suspend fun getSubscriptionIdInternal(serviceId: Int, url: String): Long?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
internal abstract fun silentInsertAllInternal(entities: List<SubscriptionEntity>): List<Long>
|
suspend fun silentInsertAllInternal(entities: List<SubscriptionEntity>): List<Long>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open fun upsertAll(entities: List<SubscriptionEntity>): List<SubscriptionEntity> {
|
suspend fun upsertAll(entities: List<SubscriptionEntity>): List<SubscriptionEntity> {
|
||||||
val insertUidList = silentInsertAllInternal(entities)
|
val insertUidList = silentInsertAllInternal(entities)
|
||||||
|
|
||||||
insertUidList.forEachIndexed { index: Int, uidFromInsert: Long ->
|
insertUidList.forEachIndexed { index: Int, uidFromInsert: Long ->
|
||||||
|
@ -103,7 +108,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||||
entity.uid = subscriptionIdFromDb
|
entity.uid = subscriptionIdFromDb
|
||||||
|
|
||||||
update(entity)
|
updateSubscription(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -127,6 +127,7 @@ import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import coil.util.CoilUtils;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
|
@ -159,8 +160,6 @@ public final class VideoDetailFragment
|
||||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
||||||
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||||
|
|
||||||
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
|
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
private boolean showComments;
|
private boolean showComments;
|
||||||
private boolean showRelatedItems;
|
private boolean showRelatedItems;
|
||||||
|
@ -1471,7 +1470,10 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG);
|
CoilUtils.dispose(binding.detailThumbnailImageView);
|
||||||
|
CoilUtils.dispose(binding.detailSubChannelThumbnailView);
|
||||||
|
CoilUtils.dispose(binding.overlayThumbnail);
|
||||||
|
|
||||||
binding.detailThumbnailImageView.setImageBitmap(null);
|
binding.detailThumbnailImageView.setImageBitmap(null);
|
||||||
binding.detailSubChannelThumbnailView.setImageBitmap(null);
|
binding.detailSubChannelThumbnailView.setImageBitmap(null);
|
||||||
}
|
}
|
||||||
|
@ -1562,8 +1564,8 @@ public final class VideoDetailFragment
|
||||||
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
||||||
|
|
||||||
checkUpdateProgressInfo(info);
|
checkUpdateProgressInfo(info);
|
||||||
PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView,
|
||||||
.into(binding.detailThumbnailImageView);
|
info.getThumbnails());
|
||||||
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
|
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
|
||||||
binding.detailMetaInfoSeparator, disposables);
|
binding.detailMetaInfoSeparator, disposables);
|
||||||
|
|
||||||
|
@ -1613,8 +1615,8 @@ public final class VideoDetailFragment
|
||||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||||
.into(binding.detailSubChannelThumbnailView);
|
info.getUploaderAvatars());
|
||||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -1645,11 +1647,11 @@ public final class VideoDetailFragment
|
||||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||||
.into(binding.detailSubChannelThumbnailView);
|
info.getSubChannelAvatars());
|
||||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView,
|
||||||
.into(binding.detailUploaderThumbnailView);
|
info.getUploaderAvatars());
|
||||||
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
|
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2403,8 +2405,7 @@ public final class VideoDetailFragment
|
||||||
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
|
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
|
||||||
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
||||||
binding.overlayThumbnail.setImageDrawable(null);
|
binding.overlayThumbnail.setImageDrawable(null);
|
||||||
PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
|
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails);
|
||||||
.into(binding.overlayThumbnail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {
|
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {
|
||||||
|
|
|
@ -49,15 +49,16 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import coil.util.CoilUtils;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Observable;
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
@ -72,7 +73,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||||
implements StateSaver.WriteRead {
|
implements StateSaver.WriteRead {
|
||||||
|
|
||||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||||
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||||
|
@ -569,7 +569,9 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||||
@Override
|
@Override
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG);
|
CoilUtils.dispose(binding.channelAvatarView);
|
||||||
|
CoilUtils.dispose(binding.channelBannerImage);
|
||||||
|
CoilUtils.dispose(binding.subChannelAvatarView);
|
||||||
animate(binding.channelSubscribeButton, false, 100);
|
animate(binding.channelSubscribeButton, false, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,17 +582,15 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||||
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
|
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
|
||||||
|
|
||||||
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
|
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
|
||||||
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
|
CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners());
|
||||||
.into(binding.channelBannerImage);
|
|
||||||
} else {
|
} else {
|
||||||
// do not waste space for the banner, if the user disabled images or there is not one
|
// do not waste space for the banner, if the user disabled images or there is not one
|
||||||
binding.channelBannerImage.setImageDrawable(null);
|
binding.channelBannerImage.setImageDrawable(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
|
CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars());
|
||||||
.into(binding.channelAvatarView);
|
CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView,
|
||||||
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
|
result.getParentChannelAvatars());
|
||||||
.into(binding.subChannelAvatarView);
|
|
||||||
|
|
||||||
binding.channelTitleView.setText(result.getName());
|
binding.channelTitleView.setText(result.getName());
|
||||||
binding.channelSubscriberView.setVisibility(View.VISIBLE);
|
binding.channelSubscriberView.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -23,8 +23,8 @@ import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.text.TextLinkifier;
|
import org.schabi.newpipe.util.text.TextLinkifier;
|
||||||
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -82,7 +82,7 @@ public final class CommentRepliesFragment
|
||||||
final CommentsInfoItem item = commentsInfoItem;
|
final CommentsInfoItem item = commentsInfoItem;
|
||||||
|
|
||||||
// load the author avatar
|
// load the author avatar
|
||||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
|
CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars());
|
||||||
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
|
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
|
||||||
? View.VISIBLE : View.GONE);
|
? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.PlayButtonHelper;
|
import org.schabi.newpipe.util.PlayButtonHelper;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.util.text.TextEllipsizer;
|
import org.schabi.newpipe.util.text.TextEllipsizer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import coil.util.CoilUtils;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
@ -71,8 +72,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
|
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
|
||||||
implements PlaylistControlViewHolder {
|
implements PlaylistControlViewHolder {
|
||||||
|
|
||||||
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
|
|
||||||
|
|
||||||
private CompositeDisposable disposables;
|
private CompositeDisposable disposables;
|
||||||
private Subscription bookmarkReactor;
|
private Subscription bookmarkReactor;
|
||||||
private AtomicBoolean isBookmarkButtonReady;
|
private AtomicBoolean isBookmarkButtonReady;
|
||||||
|
@ -276,7 +275,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
||||||
animate(headerBinding.getRoot(), false, 200);
|
animate(headerBinding.getRoot(), false, 200);
|
||||||
animateHideRecyclerViewAllowingScrolling(itemsList);
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
|
||||||
PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG);
|
CoilUtils.dispose(headerBinding.uploaderAvatarView);
|
||||||
animate(headerBinding.uploaderLayout, false, 200);
|
animate(headerBinding.uploaderLayout, false, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,8 +326,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
||||||
R.drawable.ic_radio)
|
R.drawable.ic_radio)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
|
CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView,
|
||||||
.into(headerBinding.uploaderAvatarView);
|
result.getUploaderAvatars());
|
||||||
}
|
}
|
||||||
|
|
||||||
streamCount = result.getStreamCount();
|
streamCount = result.getStreamCount();
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
package org.schabi.newpipe.info_list
|
package org.schabi.newpipe.info_list
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import com.xwray.groupie.viewbinding.BindableItem
|
||||||
import android.widget.TextView
|
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
||||||
import com.xwray.groupie.GroupieViewHolder
|
|
||||||
import com.xwray.groupie.Item
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.databinding.ItemStreamSegmentBinding
|
||||||
import org.schabi.newpipe.extractor.stream.StreamSegment
|
import org.schabi.newpipe.extractor.stream.StreamSegment
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper
|
import org.schabi.newpipe.util.image.CoilHelper
|
||||||
|
|
||||||
class StreamSegmentItem(
|
class StreamSegmentItem(
|
||||||
private val item: StreamSegment,
|
private val item: StreamSegment,
|
||||||
private val onClick: StreamSegmentAdapter.StreamSegmentListener
|
private val onClick: StreamSegmentAdapter.StreamSegmentListener
|
||||||
) : Item<GroupieViewHolder>() {
|
) : BindableItem<ItemStreamSegmentBinding>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PAYLOAD_SELECT = 1
|
const val PAYLOAD_SELECT = 1
|
||||||
|
@ -21,31 +20,32 @@ class StreamSegmentItem(
|
||||||
|
|
||||||
var isSelected = false
|
var isSelected = false
|
||||||
|
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) {
|
||||||
item.previewUrl?.let {
|
CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl)
|
||||||
PicassoHelper.loadThumbnail(it)
|
viewBinding.textViewTitle.text = item.title
|
||||||
.into(viewHolder.root.findViewById<ImageView>(R.id.previewImage))
|
|
||||||
}
|
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
|
|
||||||
if (item.channelName == null) {
|
if (item.channelName == null) {
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.GONE
|
viewBinding.textViewChannel.visibility = View.GONE
|
||||||
// When the channel name is displayed there is less space
|
// When the channel name is displayed there is less space
|
||||||
// and thus the segment title needs to be only one line height.
|
// and thus the segment title needs to be only one line height.
|
||||||
// But when there is no channel name displayed, the title can be two lines long.
|
// But when there is no channel name displayed, the title can be two lines long.
|
||||||
// The default maxLines value is set to 1 to display all elements in the AS preview,
|
// The default maxLines value is set to 1 to display all elements in the AS preview,
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).maxLines = 2
|
viewBinding.textViewTitle.maxLines = 2
|
||||||
} else {
|
} else {
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).text = item.channelName
|
viewBinding.textViewChannel.text = item.channelName
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.VISIBLE
|
viewBinding.textViewChannel.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
|
viewBinding.textViewStartSeconds.text =
|
||||||
Localization.getDurationString(item.startTimeSeconds.toLong())
|
Localization.getDurationString(item.startTimeSeconds.toLong())
|
||||||
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||||
viewHolder.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
|
viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
|
||||||
viewHolder.root.isSelected = isSelected
|
viewBinding.root.isSelected = isSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
override fun bind(
|
||||||
|
viewHolder: GroupieViewHolder<ItemStreamSegmentBinding>,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any>
|
||||||
|
) {
|
||||||
if (payloads.contains(PAYLOAD_SELECT)) {
|
if (payloads.contains(PAYLOAD_SELECT)) {
|
||||||
viewHolder.root.isSelected = isSelected
|
viewHolder.root.isSelected = isSelected
|
||||||
return
|
return
|
||||||
|
@ -54,4 +54,6 @@ class StreamSegmentItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLayout() = R.layout.item_stream_segment
|
override fun getLayout() = R.layout.item_stream_segment
|
||||||
|
|
||||||
|
override fun initializeViewBinding(view: View) = ItemStreamSegmentBinding.bind(view)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
import org.schabi.newpipe.extractor.utils.Utils;
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||||
private final ImageView itemThumbnailView;
|
private final ImageView itemThumbnailView;
|
||||||
|
@ -56,7 +56,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemAdditionalDetailView.setText(getDetailLine(item));
|
itemAdditionalDetailView.setText(getDetailLine(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getThumbnails());
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnChannelSelectedListener() != null) {
|
if (itemBuilder.getOnChannelSelectedListener() != null) {
|
||||||
|
|
|
@ -23,8 +23,8 @@ import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
|
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
|
||||||
import org.schabi.newpipe.util.text.TextEllipsizer;
|
import org.schabi.newpipe.util.text.TextEllipsizer;
|
||||||
|
|
||||||
|
@ -79,14 +79,12 @@ public class CommentInfoItemHolder extends InfoItemHolder {
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem,
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
final HistoryRecordManager historyRecordManager) {
|
final HistoryRecordManager historyRecordManager) {
|
||||||
if (!(infoItem instanceof CommentsInfoItem)) {
|
if (!(infoItem instanceof CommentsInfoItem item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
|
||||||
|
|
||||||
|
|
||||||
// load the author avatar
|
// load the author avatar
|
||||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getUploaderAvatars());
|
||||||
if (ImageStrategy.shouldLoadImages()) {
|
if (ImageStrategy.shouldLoadImages()) {
|
||||||
itemThumbnailView.setVisibility(View.VISIBLE);
|
itemThumbnailView.setVisibility(View.VISIBLE);
|
||||||
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
|
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
|
||||||
|
|
|
@ -9,8 +9,8 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
public final ImageView itemThumbnailView;
|
public final ImageView itemThumbnailView;
|
||||||
|
@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||||
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
|
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
|
||||||
itemUploaderView.setText(item.getUploaderName());
|
itemUploaderView.setText(item.getUploaderName());
|
||||||
|
|
||||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnails());
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ import org.schabi.newpipe.ktx.ViewUtils;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -87,7 +87,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||||
PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, item.getThumbnails());
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
||||||
|
|
13
app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt
Normal file
13
app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package org.schabi.newpipe.ktx
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Rect
|
||||||
|
import androidx.core.graphics.BitmapCompat
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun Bitmap.scale(
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
srcRect: Rect? = null,
|
||||||
|
scaleInLinearSpace: Boolean = true,
|
||||||
|
) = BitmapCompat.createScaledBitmap(this, width, height, srcRect, scaleInLinearSpace)
|
|
@ -73,27 +73,22 @@ class FeedDatabaseManager(context: Context) {
|
||||||
outdatedThreshold: OffsetDateTime
|
outdatedThreshold: OffsetDateTime
|
||||||
) = feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
|
) = feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
|
||||||
|
|
||||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
suspend fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||||
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
||||||
|
|
||||||
fun doesStreamExist(stream: StreamInfoItem): Boolean {
|
suspend fun doesStreamExist(stream: StreamInfoItem): Boolean {
|
||||||
return streamTable.exists(stream.serviceId, stream.url)
|
return streamTable.exists(stream.serviceId, stream.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertAll(
|
suspend fun upsertAll(
|
||||||
subscriptionId: Long,
|
subscriptionId: Long,
|
||||||
items: List<StreamInfoItem>,
|
items: List<StreamInfoItem>,
|
||||||
oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE
|
oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE
|
||||||
) {
|
) {
|
||||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
val itemsToInsert = items.filter {
|
||||||
loop@ for (streamItem in items) {
|
val uploadDate = it.uploadDate
|
||||||
val uploadDate = streamItem.uploadDate
|
uploadDate == null && it.streamType == StreamType.LIVE_STREAM ||
|
||||||
|
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate
|
||||||
itemsToInsert += when {
|
|
||||||
uploadDate == null && streamItem.streamType == StreamType.LIVE_STREAM -> streamItem
|
|
||||||
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate -> streamItem
|
|
||||||
else -> continue@loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
feedTable.unlinkOldLivestreams(subscriptionId)
|
feedTable.unlinkOldLivestreams(subscriptionId)
|
||||||
|
@ -111,12 +106,12 @@ class FeedDatabaseManager(context: Context) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
|
suspend fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
|
||||||
feedTable.unlinkStreamsOlderThan(oldestAllowedDate)
|
feedTable.unlinkStreamsOlderThan(oldestAllowedDate)
|
||||||
streamTable.deleteOrphans()
|
streamTable.deleteOrphans()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
suspend fun clear() {
|
||||||
feedTable.deleteAll()
|
feedTable.deleteAll()
|
||||||
val deletedOrphans = streamTable.deleteOrphans()
|
val deletedOrphans = streamTable.deleteOrphans()
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import androidx.core.content.edit
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -49,10 +50,9 @@ import com.xwray.groupie.Item
|
||||||
import com.xwray.groupie.OnItemClickListener
|
import com.xwray.groupie.OnItemClickListener
|
||||||
import com.xwray.groupie.OnItemLongClickListener
|
import com.xwray.groupie.OnItemLongClickListener
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.schabi.newpipe.NewPipeDatabase
|
import org.schabi.newpipe.NewPipeDatabase
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
|
@ -253,7 +253,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
viewModel.getShowFutureItemsFromPreferences()
|
viewModel.getShowFutureItemsFromPreferences()
|
||||||
)
|
)
|
||||||
|
|
||||||
AlertDialog.Builder(context!!)
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.feed_hide_streams_title)
|
.setTitle(R.string.feed_hide_streams_title)
|
||||||
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
||||||
checkedDialogItems[which] = isChecked
|
checkedDialogItems[which] = isChecked
|
||||||
|
@ -453,24 +453,20 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
if (t is FeedLoadService.RequestException &&
|
if (t is FeedLoadService.RequestException &&
|
||||||
t.cause is ContentNotAvailableException
|
t.cause is ContentNotAvailableException
|
||||||
) {
|
) {
|
||||||
disposables.add(
|
lifecycleScope.launch(
|
||||||
Single.fromCallable {
|
CoroutineExceptionHandler { _, throwable ->
|
||||||
NewPipeDatabase.getInstance(requireContext()).subscriptionDAO()
|
Log.e(TAG, "Unable to process", throwable)
|
||||||
.getSubscription(t.subscriptionId)
|
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
) {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
val subscriptionEntity = NewPipeDatabase.getInstance(requireContext())
|
||||||
.subscribe(
|
.subscriptionDAO()
|
||||||
{ subscriptionEntity ->
|
.getSubscription(t.subscriptionId)
|
||||||
handleFeedNotAvailable(
|
handleFeedNotAvailable(
|
||||||
subscriptionEntity,
|
subscriptionEntity,
|
||||||
t.cause,
|
t.cause,
|
||||||
errors.subList(i + 1, errors.size)
|
errors.subList(i + 1, errors.size)
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
{ throwable -> Log.e(TAG, "Unable to process", throwable) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// this will be called on the remaining errors by handleFeedNotAvailable()
|
// this will be called on the remaining errors by handleFeedNotAvailable()
|
||||||
return@handleItemsErrors
|
return@handleItemsErrors
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_STREAM
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM
|
import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil
|
import org.schabi.newpipe.util.StreamTypeUtil
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper
|
import org.schabi.newpipe.util.image.CoilHelper
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ data class StreamItem(
|
||||||
viewBinding.itemProgressView.visibility = View.GONE
|
viewBinding.itemProgressView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView)
|
CoilHelper.loadThumbnail(viewBinding.itemThumbnailView, stream.thumbnailUrl)
|
||||||
|
|
||||||
if (itemVersion != ItemVersion.MINI) {
|
if (itemVersion != ItemVersion.MINI) {
|
||||||
viewBinding.itemAdditionalDetails.text =
|
viewBinding.itemAdditionalDetails.text =
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
@ -16,20 +15,17 @@ import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import com.squareup.picasso.Target
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
|
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper
|
import org.schabi.newpipe.util.image.CoilHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for everything related to show notifications about new streams to the user.
|
* Helper for everything related to show notifications about new streams to the user.
|
||||||
*/
|
*/
|
||||||
class NotificationHelper(val context: Context) {
|
class NotificationHelper(val context: Context) {
|
||||||
private val manager = NotificationManagerCompat.from(context)
|
private val manager = NotificationManagerCompat.from(context)
|
||||||
private val iconLoadingTargets = ArrayList<Target>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show notifications for new streams from a single channel. The individual notifications are
|
* Show notifications for new streams from a single channel. The individual notifications are
|
||||||
|
@ -38,7 +34,7 @@ class NotificationHelper(val context: Context) {
|
||||||
* Opening the summary notification will open the corresponding channel page. Opening the
|
* Opening the summary notification will open the corresponding channel page. Opening the
|
||||||
* individual notifications will open the corresponding video.
|
* individual notifications will open the corresponding video.
|
||||||
*/
|
*/
|
||||||
fun displayNewStreamsNotifications(data: FeedUpdateInfo) {
|
suspend fun displayNewStreamsNotifications(data: FeedUpdateInfo) {
|
||||||
val newStreams = data.newStreams
|
val newStreams = data.newStreams
|
||||||
val summary = context.resources.getQuantityString(
|
val summary = context.resources.getQuantityString(
|
||||||
R.plurals.new_streams, newStreams.size, newStreams.size
|
R.plurals.new_streams, newStreams.size, newStreams.size
|
||||||
|
@ -68,51 +64,23 @@ class NotificationHelper(val context: Context) {
|
||||||
summaryBuilder.setStyle(style)
|
summaryBuilder.setStyle(style)
|
||||||
|
|
||||||
// open the channel page when clicking on the summary notification
|
// open the channel page when clicking on the summary notification
|
||||||
|
val intent = NavigationHelper
|
||||||
|
.getChannelIntent(context, data.serviceId, data.url)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
summaryBuilder.setContentIntent(
|
summaryBuilder.setContentIntent(
|
||||||
PendingIntentCompat.getActivity(
|
PendingIntentCompat.getActivity(context, data.pseudoId, intent, 0, false)
|
||||||
context,
|
|
||||||
data.pseudoId,
|
|
||||||
NavigationHelper
|
|
||||||
.getChannelIntent(context, data.serviceId, data.url)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
||||||
0,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// a Target is like a listener for image loading events
|
val avatarIcon =
|
||||||
val target = object : Target {
|
CoilHelper.loadBitmap(context, data.avatarUrl, R.drawable.ic_newpipe_triangle_white)
|
||||||
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
|
||||||
// set channel icon only if there is actually one (for Android versions < 7.0)
|
|
||||||
summaryBuilder.setLargeIcon(bitmap)
|
|
||||||
|
|
||||||
// Show individual stream notifications, set channel icon only if there is actually
|
summaryBuilder.setLargeIcon(avatarIcon)
|
||||||
// one
|
|
||||||
showStreamNotifications(newStreams, data.serviceId, bitmap)
|
|
||||||
// Show summary notification
|
|
||||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
|
||||||
|
|
||||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
// Show individual stream notifications, set channel icon only if there is actually
|
||||||
}
|
// one
|
||||||
|
showStreamNotifications(newStreams, data.serviceId, avatarIcon)
|
||||||
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
// Show summary notification
|
||||||
// Show individual stream notifications
|
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||||
showStreamNotifications(newStreams, data.serviceId, null)
|
|
||||||
// Show summary notification
|
|
||||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
|
||||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPrepareLoad(placeHolderDrawable: Drawable) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the target to the list to hold a strong reference and prevent it from being garbage
|
|
||||||
// collected, since Picasso only holds weak references to targets
|
|
||||||
iconLoadingTargets.add(target)
|
|
||||||
|
|
||||||
PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showStreamNotifications(
|
private fun showStreamNotifications(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
|
@ -11,9 +12,6 @@ import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.PeriodicWorkRequest
|
import androidx.work.PeriodicWorkRequest
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.rxjava3.RxWorker
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import org.schabi.newpipe.App
|
import org.schabi.newpipe.App
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.error.ErrorInfo
|
import org.schabi.newpipe.error.ErrorInfo
|
||||||
|
@ -30,46 +28,38 @@ import java.util.concurrent.TimeUnit
|
||||||
class NotificationWorker(
|
class NotificationWorker(
|
||||||
appContext: Context,
|
appContext: Context,
|
||||||
workerParams: WorkerParameters,
|
workerParams: WorkerParameters,
|
||||||
) : RxWorker(appContext, workerParams) {
|
) : CoroutineWorker(appContext, workerParams) {
|
||||||
|
|
||||||
private val notificationHelper by lazy {
|
private val notificationHelper by lazy {
|
||||||
NotificationHelper(appContext)
|
NotificationHelper(appContext)
|
||||||
}
|
}
|
||||||
private val feedLoadManager = FeedLoadManager(appContext)
|
private val feedLoadManager = FeedLoadManager(appContext)
|
||||||
|
|
||||||
override fun createWork(): Single<Result> = if (areNotificationsEnabled(applicationContext)) {
|
override suspend fun doWork(): Result {
|
||||||
feedLoadManager.startLoading(
|
return if (areNotificationsEnabled(applicationContext)) {
|
||||||
ignoreOutdatedThreshold = true,
|
try {
|
||||||
groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED
|
showLoadingFeedForegroundNotification()
|
||||||
)
|
val feedUpdateInfoList = feedLoadManager
|
||||||
.doOnSubscribe { showLoadingFeedForegroundNotification() }
|
.startLoading(FeedLoadManager.GROUP_NOTIFICATION_ENABLED, true)
|
||||||
.map { feed ->
|
// filter out feedUpdateInfo items (i.e. channels) with nothing new
|
||||||
// filter out feedUpdateInfo items (i.e. channels) with nothing new
|
.mapNotNull { it.value?.takeIf { it.newStreams.isNotEmpty() } }
|
||||||
feed.mapNotNull {
|
|
||||||
it.value?.takeIf { feedUpdateInfo ->
|
|
||||||
feedUpdateInfo.newStreams.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread
|
|
||||||
.map { feedUpdateInfoList ->
|
|
||||||
// display notifications for each feedUpdateInfo (i.e. channel)
|
// display notifications for each feedUpdateInfo (i.e. channel)
|
||||||
feedUpdateInfoList.forEach { feedUpdateInfo ->
|
feedUpdateInfoList.forEach {
|
||||||
notificationHelper.displayNewStreamsNotifications(feedUpdateInfo)
|
notificationHelper.displayNewStreamsNotifications(it)
|
||||||
}
|
}
|
||||||
return@map Result.success()
|
Result.success()
|
||||||
}
|
} catch (e: Exception) {
|
||||||
.doOnError { throwable ->
|
Log.e(TAG, "Error while displaying streams notifications", e)
|
||||||
Log.e(TAG, "Error while displaying streams notifications", throwable)
|
|
||||||
ErrorUtil.createNotification(
|
ErrorUtil.createNotification(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
ErrorInfo(throwable, UserAction.NEW_STREAMS_NOTIFICATIONS, "main worker")
|
ErrorInfo(e, UserAction.NEW_STREAMS_NOTIFICATIONS, "main worker")
|
||||||
)
|
)
|
||||||
|
Result.failure()
|
||||||
}
|
}
|
||||||
.onErrorReturnItem(Result.failure())
|
} else {
|
||||||
} else {
|
Result.success()
|
||||||
// the user can disable streams notifications in the device's app settings
|
}
|
||||||
Single.just(Result.success())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLoadingFeedForegroundNotification() {
|
private fun showLoadingFeedForegroundNotification() {
|
||||||
|
|
|
@ -3,14 +3,20 @@ package org.schabi.newpipe.local.feed.service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import androidx.room.withTransaction
|
||||||
import io.reactivex.rxjava3.core.Completable
|
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.core.Notification
|
import io.reactivex.rxjava3.core.Notification
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import io.reactivex.rxjava3.functions.Consumer
|
|
||||||
import io.reactivex.rxjava3.processors.PublishProcessor
|
import io.reactivex.rxjava3.processors.PublishProcessor
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.take
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.subscription.NotificationMode
|
import org.schabi.newpipe.database.subscription.NotificationMode
|
||||||
|
@ -56,10 +62,10 @@ class FeedLoadManager(private val context: Context) {
|
||||||
* within the `feed_update_threshold` are checked for updates. This threshold can be set by
|
* within the `feed_update_threshold` are checked for updates. This threshold can be set by
|
||||||
* the user in the app settings. When `true`, all subscriptions are checked for new streams.
|
* the user in the app settings. When `true`, all subscriptions are checked for new streams.
|
||||||
*/
|
*/
|
||||||
fun startLoading(
|
suspend fun startLoading(
|
||||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
ignoreOutdatedThreshold: Boolean = false,
|
ignoreOutdatedThreshold: Boolean = false,
|
||||||
): Single<List<Notification<FeedUpdateInfo>>> {
|
): List<Notification<FeedUpdateInfo>> {
|
||||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val useFeedExtractor = defaultSharedPreferences.getBoolean(
|
val useFeedExtractor = defaultSharedPreferences.getBoolean(
|
||||||
context.getString(R.string.feed_use_dedicated_fetch_method_key),
|
context.getString(R.string.feed_use_dedicated_fetch_method_key),
|
||||||
|
@ -91,36 +97,72 @@ class FeedLoadManager(private val context: Context) {
|
||||||
else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold)
|
else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
return outdatedSubscriptions
|
val notifications = withContext(Dispatchers.IO) {
|
||||||
.take(1)
|
outdatedSubscriptions
|
||||||
.doOnNext {
|
.take(1)
|
||||||
currentProgress.set(0)
|
.onEach {
|
||||||
maxProgress.set(it.size)
|
currentProgress.set(0)
|
||||||
|
maxProgress.set(it.size)
|
||||||
|
}
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.onEach {
|
||||||
|
notificationUpdater.onNext("")
|
||||||
|
broadcastProgress()
|
||||||
|
}
|
||||||
|
.takeWhile { !cancelSignal.get() }
|
||||||
|
.filter { !cancelSignal.get() }
|
||||||
|
.flatMapConcat { it ->
|
||||||
|
it.map { loadStreams(it, useFeedExtractor, defaultSharedPreferences) }
|
||||||
|
.asFlow()
|
||||||
|
}
|
||||||
|
.onEach {
|
||||||
|
currentProgress.incrementAndGet()
|
||||||
|
notificationUpdater.onNext(it.value?.name.orEmpty())
|
||||||
|
|
||||||
|
broadcastProgress()
|
||||||
|
}
|
||||||
|
.buffer(BUFFER_COUNT_BEFORE_INSERT)
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
feedDatabaseManager.database().withTransaction {
|
||||||
|
for (notification in notifications) {
|
||||||
|
when {
|
||||||
|
notification.isOnNext -> {
|
||||||
|
val info = notification.value!!
|
||||||
|
|
||||||
|
notification.value!!.newStreams = filterNewStreams(info.streams)
|
||||||
|
|
||||||
|
feedDatabaseManager.upsertAll(info.uid, info.streams)
|
||||||
|
subscriptionManager.updateFromInfo(info)
|
||||||
|
|
||||||
|
if (info.errors.isNotEmpty()) {
|
||||||
|
feedResultsHolder.addErrors(
|
||||||
|
info.errors.map {
|
||||||
|
FeedLoadService.RequestException(
|
||||||
|
info.uid,
|
||||||
|
"${info.serviceId}:${info.url}",
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
feedDatabaseManager.markAsOutdated(info.uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notification.isOnError -> {
|
||||||
|
val error = notification.error
|
||||||
|
feedResultsHolder.addError(error!!)
|
||||||
|
|
||||||
|
if (error is FeedLoadService.RequestException) {
|
||||||
|
feedDatabaseManager.markAsOutdated(error.subscriptionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.filter { it.isNotEmpty() }
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext {
|
postProcessFeed()
|
||||||
notificationUpdater.onNext("")
|
return notifications
|
||||||
broadcastProgress()
|
|
||||||
}
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.flatMap { Flowable.fromIterable(it) }
|
|
||||||
.takeWhile { !cancelSignal.get() }
|
|
||||||
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
|
|
||||||
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
|
|
||||||
.filter { !cancelSignal.get() }
|
|
||||||
.map { subscriptionEntity ->
|
|
||||||
loadStreams(subscriptionEntity, useFeedExtractor, defaultSharedPreferences)
|
|
||||||
}
|
|
||||||
.sequential()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext(NotificationConsumer())
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.buffer(BUFFER_COUNT_BEFORE_INSERT)
|
|
||||||
.doOnNext(DatabaseConsumer())
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.toList()
|
|
||||||
.flatMap { x -> postProcessFeed().toSingleDefault(x.flatten()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
|
@ -243,79 +285,29 @@ class FeedLoadManager(private val context: Context) {
|
||||||
* Remove streams from the feed which are older than [FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE].
|
* Remove streams from the feed which are older than [FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE].
|
||||||
* Remove streams from the database which are not linked / used by any table.
|
* Remove streams from the database which are not linked / used by any table.
|
||||||
*/
|
*/
|
||||||
private fun postProcessFeed() = Completable.fromRunnable {
|
private suspend fun postProcessFeed() {
|
||||||
FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message))
|
withContext(Dispatchers.IO) {
|
||||||
feedDatabaseManager.removeOrphansOrOlderStreams()
|
FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message))
|
||||||
|
feedDatabaseManager.removeOrphansOrOlderStreams()
|
||||||
|
|
||||||
|
FeedEventManager.postEvent(FeedEventManager.Event.SuccessResultEvent(feedResultsHolder.itemsErrors))
|
||||||
|
}
|
||||||
|
|
||||||
FeedEventManager.postEvent(FeedEventManager.Event.SuccessResultEvent(feedResultsHolder.itemsErrors))
|
|
||||||
}.doOnSubscribe {
|
|
||||||
currentProgress.set(-1)
|
currentProgress.set(-1)
|
||||||
maxProgress.set(-1)
|
maxProgress.set(-1)
|
||||||
|
|
||||||
notificationUpdater.onNext(context.getString(R.string.feed_processing_message))
|
notificationUpdater.onNext(context.getString(R.string.feed_processing_message))
|
||||||
FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message))
|
FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message))
|
||||||
}.subscribeOn(Schedulers.io())
|
|
||||||
|
|
||||||
private inner class NotificationConsumer : Consumer<Notification<FeedUpdateInfo>> {
|
|
||||||
override fun accept(item: Notification<FeedUpdateInfo>) {
|
|
||||||
currentProgress.incrementAndGet()
|
|
||||||
notificationUpdater.onNext(item.value?.name.orEmpty())
|
|
||||||
|
|
||||||
broadcastProgress()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class DatabaseConsumer : Consumer<List<Notification<FeedUpdateInfo>>> {
|
private suspend fun filterNewStreams(list: List<StreamInfoItem>): List<StreamInfoItem> {
|
||||||
|
return list.filter {
|
||||||
override fun accept(list: List<Notification<FeedUpdateInfo>>) {
|
!feedDatabaseManager.doesStreamExist(it) &&
|
||||||
feedDatabaseManager.database().runInTransaction {
|
it.uploadDate != null &&
|
||||||
for (notification in list) {
|
// Streams older than this date are automatically removed from the feed.
|
||||||
when {
|
// Therefore, streams which are not in the database,
|
||||||
notification.isOnNext -> {
|
// but older than this date, are considered old.
|
||||||
val info = notification.value!!
|
it.uploadDate!!.offsetDateTime() > FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE
|
||||||
|
|
||||||
notification.value!!.newStreams = filterNewStreams(info.streams)
|
|
||||||
|
|
||||||
feedDatabaseManager.upsertAll(info.uid, info.streams)
|
|
||||||
subscriptionManager.updateFromInfo(info)
|
|
||||||
|
|
||||||
if (info.errors.isNotEmpty()) {
|
|
||||||
feedResultsHolder.addErrors(
|
|
||||||
info.errors.map {
|
|
||||||
FeedLoadService.RequestException(
|
|
||||||
info.uid,
|
|
||||||
"${info.serviceId}:${info.url}",
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
feedDatabaseManager.markAsOutdated(info.uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notification.isOnError -> {
|
|
||||||
val error = notification.error
|
|
||||||
feedResultsHolder.addError(error!!)
|
|
||||||
|
|
||||||
if (error is FeedLoadService.RequestException) {
|
|
||||||
feedDatabaseManager.markAsOutdated(error.subscriptionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterNewStreams(list: List<StreamInfoItem>): List<StreamInfoItem> {
|
|
||||||
return list.filter {
|
|
||||||
!feedDatabaseManager.doesStreamExist(it) &&
|
|
||||||
it.uploadDate != null &&
|
|
||||||
// Streams older than this date are automatically removed from the feed.
|
|
||||||
// Therefore, streams which are not in the database,
|
|
||||||
// but older than this date, are considered old.
|
|
||||||
it.uploadDate!!.offsetDateTime().isAfter(
|
|
||||||
FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,22 +19,24 @@
|
||||||
|
|
||||||
package org.schabi.newpipe.local.feed.service
|
package org.schabi.newpipe.local.feed.service
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
|
import androidx.lifecycle.LifecycleService
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import io.reactivex.rxjava3.functions.Function
|
import io.reactivex.rxjava3.functions.Function
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.schabi.newpipe.App
|
import org.schabi.newpipe.App
|
||||||
import org.schabi.newpipe.MainActivity.DEBUG
|
import org.schabi.newpipe.MainActivity.DEBUG
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
@ -43,7 +45,7 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultE
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class FeedLoadService : Service() {
|
class FeedLoadService : LifecycleService() {
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = FeedLoadService::class.java.simpleName
|
private val TAG = FeedLoadService::class.java.simpleName
|
||||||
const val NOTIFICATION_ID = 7293450
|
const val NOTIFICATION_ID = 7293450
|
||||||
|
@ -72,35 +74,34 @@ class FeedLoadService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"onStartCommand() called with: intent = [" + intent + "]," +
|
"onStartCommand() called with: intent = [$intent], flags = [$flags], " +
|
||||||
" flags = [" + flags + "], startId = [" + startId + "]"
|
"startId = [$startId]"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent == null || loadingDisposable != null) {
|
if (intent == null) {
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
setupNotification()
|
setupNotification()
|
||||||
setupBroadcastReceiver()
|
setupBroadcastReceiver()
|
||||||
|
|
||||||
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
lifecycleScope.launch(
|
||||||
loadingDisposable = feedLoadManager.startLoading(groupId)
|
CoroutineExceptionHandler { _, throwable ->
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
Log.e(TAG, "Error while storing result", throwable)
|
||||||
.doOnSubscribe {
|
handleError(throwable)
|
||||||
startForeground(NOTIFICATION_ID, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
.subscribe { _, error: Throwable? -> // explicitly mark error as nullable
|
|
||||||
if (error != null) {
|
|
||||||
Log.e(TAG, "Error while storing result", error)
|
|
||||||
handleError(error)
|
|
||||||
return@subscribe
|
|
||||||
}
|
|
||||||
stopService()
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
||||||
|
startForeground(NOTIFICATION_ID, notificationBuilder.build())
|
||||||
|
feedLoadManager.startLoading(groupId)
|
||||||
|
stopService()
|
||||||
|
}
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,10 +117,6 @@ class FeedLoadService : Service() {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Loading & Handling
|
// Loading & Handling
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -312,7 +312,6 @@ public class HistoryRecordManager {
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
public Single<Integer> removeOrphanedRecords() {
|
public Single<Integer> removeOrphanedRecords() {
|
||||||
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
|
return Single.fromCallable(streamTable::deleteOrphansBlocking).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@ -30,17 +30,16 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager,
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
final DateTimeFormatter dateTimeFormatter) {
|
||||||
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
if (!(localItem instanceof PlaylistMetadataEntry item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
|
||||||
|
|
||||||
itemTitleView.setText(item.name);
|
itemTitleView.setText(item.name);
|
||||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||||
itemStreamCountView.getContext(), item.streamCount));
|
itemStreamCountView.getContext(), item.streamCount));
|
||||||
itemUploaderView.setVisibility(View.INVISIBLE);
|
itemUploaderView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.thumbnailUrl);
|
||||||
|
|
||||||
if (item instanceof PlaylistDuplicatesEntry
|
if (item instanceof PlaylistDuplicatesEntry
|
||||||
&& ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) {
|
&& ((PlaylistDuplicatesEntry) item).timesStreamIsContained > 0) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
@ -83,8 +83,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||||
.into(itemThumbnailView);
|
item.getStreamEntity().getThumbnailUrl());
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
@ -117,8 +117,8 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||||
.into(itemThumbnailView);
|
item.getStreamEntity().getThumbnailUrl());
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@ -29,10 +29,9 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager,
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
final DateTimeFormatter dateTimeFormatter) {
|
||||||
if (!(localItem instanceof PlaylistRemoteEntity)) {
|
if (!(localItem instanceof PlaylistRemoteEntity item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
|
||||||
|
|
||||||
itemTitleView.setText(item.getName());
|
itemTitleView.setText(item.getName());
|
||||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||||
|
@ -45,7 +44,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||||
itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId()));
|
itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl());
|
||||||
|
|
||||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ package org.schabi.newpipe.local.subscription
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
|
import androidx.room.withTransaction
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Completable
|
import io.reactivex.rxjava3.core.Completable
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.schabi.newpipe.NewPipeDatabase
|
import org.schabi.newpipe.NewPipeDatabase
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
|
@ -26,7 +28,7 @@ class SubscriptionManager(context: Context) {
|
||||||
private val feedDatabaseManager = FeedDatabaseManager(context)
|
private val feedDatabaseManager = FeedDatabaseManager(context)
|
||||||
|
|
||||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||||
fun subscriptions() = subscriptionTable.all
|
fun subscriptions() = subscriptionTable.getAll()
|
||||||
|
|
||||||
fun getSubscriptions(
|
fun getSubscriptions(
|
||||||
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
|
@ -44,16 +46,18 @@ class SubscriptionManager(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
||||||
else -> subscriptionTable.all
|
else -> subscriptionTable.getAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertAll(infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>): List<SubscriptionEntity> {
|
fun upsertAll(
|
||||||
|
infoList: List<Pair<ChannelInfo, List<ChannelTabInfo>>>
|
||||||
|
): List<SubscriptionEntity> = runBlocking {
|
||||||
val listEntities = subscriptionTable.upsertAll(
|
val listEntities = subscriptionTable.upsertAll(
|
||||||
infoList.map { SubscriptionEntity.from(it.first) }
|
infoList.map { SubscriptionEntity.from(it.first) }
|
||||||
)
|
)
|
||||||
|
|
||||||
database.runInTransaction {
|
database.withTransaction {
|
||||||
infoList.forEachIndexed { index, info ->
|
infoList.forEachIndexed { index, info ->
|
||||||
info.second.forEach {
|
info.second.forEach {
|
||||||
feedDatabaseManager.upsertAll(
|
feedDatabaseManager.upsertAll(
|
||||||
|
@ -64,7 +68,7 @@ class SubscriptionManager(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return listEntities
|
listEntities
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateChannelInfo(info: ChannelInfo): Completable =
|
fun updateChannelInfo(info: ChannelInfo): Completable =
|
||||||
|
@ -96,7 +100,7 @@ class SubscriptionManager(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFromInfo(info: FeedUpdateInfo) {
|
suspend fun updateFromInfo(info: FeedUpdateInfo) {
|
||||||
val subscriptionEntity = subscriptionTable.getSubscription(info.uid)
|
val subscriptionEntity = subscriptionTable.getSubscription(info.uid)
|
||||||
|
|
||||||
subscriptionEntity.name = info.name
|
subscriptionEntity.name = info.name
|
||||||
|
@ -108,7 +112,7 @@ class SubscriptionManager(context: Context) {
|
||||||
info.description?.let { subscriptionEntity.description = it }
|
info.description?.let { subscriptionEntity.description = it }
|
||||||
info.subscriberCount?.let { subscriptionEntity.subscriberCount = it }
|
info.subscriberCount?.let { subscriptionEntity.subscriberCount = it }
|
||||||
|
|
||||||
subscriptionTable.update(subscriptionEntity)
|
subscriptionTable.updateSubscription(subscriptionEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.OnClickGesture
|
import org.schabi.newpipe.util.OnClickGesture
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper
|
import org.schabi.newpipe.util.image.CoilHelper
|
||||||
|
|
||||||
class ChannelItem(
|
class ChannelItem(
|
||||||
private val infoItem: ChannelInfoItem,
|
private val infoItem: ChannelInfoItem,
|
||||||
|
@ -39,7 +39,7 @@ class ChannelItem(
|
||||||
itemChannelDescriptionView.text = infoItem.description
|
itemChannelDescriptionView.text = infoItem.description
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView)
|
CoilHelper.loadAvatar(itemThumbnailView, infoItem.thumbnails)
|
||||||
|
|
||||||
gesturesListener?.run {
|
gesturesListener?.run {
|
||||||
viewHolder.root.setOnClickListener { selected(infoItem) }
|
viewHolder.root.setOnClickListener { selected(infoItem) }
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding
|
import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding
|
||||||
import org.schabi.newpipe.ktx.AnimationType
|
import org.schabi.newpipe.ktx.AnimationType
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper
|
import org.schabi.newpipe.util.image.CoilHelper
|
||||||
|
|
||||||
data class PickerSubscriptionItem(
|
data class PickerSubscriptionItem(
|
||||||
val subscriptionEntity: SubscriptionEntity,
|
val subscriptionEntity: SubscriptionEntity,
|
||||||
|
@ -21,7 +21,7 @@ data class PickerSubscriptionItem(
|
||||||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||||
|
|
||||||
override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) {
|
override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) {
|
||||||
PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView)
|
CoilHelper.loadAvatar(viewBinding.thumbnailView, subscriptionEntity.avatarUrl)
|
||||||
viewBinding.titleView.text = subscriptionEntity.name
|
viewBinding.titleView.text = subscriptionEntity.name
|
||||||
viewBinding.selectedHighlight.isVisible = isSelected
|
viewBinding.selectedHighlight.isVisible = isSelected
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ import android.view.LayoutInflater;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.graphics.drawable.DrawableKt;
|
||||||
import androidx.core.math.MathUtils;
|
import androidx.core.math.MathUtils;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
@ -77,8 +78,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.video.VideoSize;
|
import com.google.android.exoplayer2.video.VideoSize;
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
import com.squareup.picasso.Target;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -86,8 +85,8 @@ import org.schabi.newpipe.databinding.PlayerBinding;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
|
||||||
import org.schabi.newpipe.extractor.Image;
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
@ -118,14 +117,15 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import coil.target.Target;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Observable;
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
|
@ -174,7 +174,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static final int RENDERER_UNAVAILABLE = -1;
|
public static final int RENDERER_UNAVAILABLE = -1;
|
||||||
private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG";
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playback
|
// Playback
|
||||||
|
@ -246,12 +245,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
// This is the only listener we need for thumbnail loading, since there is always at most only
|
|
||||||
// one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
|
|
||||||
// which would otherwise be garbage collected since Picasso holds weak references to targets.
|
|
||||||
@NonNull
|
|
||||||
private final Target currentThumbnailTarget;
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -295,8 +288,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||||
|
|
||||||
currentThumbnailTarget = getCurrentThumbnailTarget();
|
|
||||||
|
|
||||||
// The UIs added here should always be present. They will be initialized when the player
|
// The UIs added here should always be present. They will be initialized when the player
|
||||||
// reaches the initialization step. Make sure the media session ui is before the
|
// reaches the initialization step. Make sure the media session ui is before the
|
||||||
// notification ui in the UIs list, since the notification depends on the media session in
|
// notification ui in the UIs list, since the notification depends on the media session in
|
||||||
|
@ -602,7 +593,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
|
|
||||||
databaseUpdateDisposable.clear();
|
databaseUpdateDisposable.clear();
|
||||||
progressUpdateDisposable.set(null);
|
progressUpdateDisposable.set(null);
|
||||||
cancelLoadingCurrentThumbnail();
|
|
||||||
|
|
||||||
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object
|
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object
|
||||||
}
|
}
|
||||||
|
@ -776,67 +766,52 @@ public final class Player implements PlaybackListener, Listener {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
//region Thumbnail loading
|
//region Thumbnail loading
|
||||||
|
|
||||||
private Target getCurrentThumbnailTarget() {
|
|
||||||
// a Picasso target is just a listener for thumbnail loading events
|
|
||||||
return new Target() {
|
|
||||||
@Override
|
|
||||||
public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap
|
|
||||||
+ " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = ["
|
|
||||||
+ from + "]");
|
|
||||||
}
|
|
||||||
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
|
||||||
onThumbnailLoaded(bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBitmapFailed(final Exception e, final Drawable errorDrawable) {
|
|
||||||
Log.e(TAG, "Thumbnail - onBitmapFailed() called", e);
|
|
||||||
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
|
||||||
onThumbnailLoaded(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareLoad(final Drawable placeHolderDrawable) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Thumbnail - onPrepareLoad() called");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadCurrentThumbnail(final List<Image> thumbnails) {
|
private void loadCurrentThumbnail(final List<Image> thumbnails) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
|
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
|
||||||
+ thumbnails.size() + "]");
|
+ thumbnails.size() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// first cancel any previous loading
|
|
||||||
cancelLoadingCurrentThumbnail();
|
|
||||||
|
|
||||||
// Unset currentThumbnail, since it is now outdated. This ensures it is not used in media
|
// Unset currentThumbnail, since it is now outdated. This ensures it is not used in media
|
||||||
// session metadata while the new thumbnail is being loaded by Picasso.
|
// session metadata while the new thumbnail is being loaded by Coil.
|
||||||
onThumbnailLoaded(null);
|
onThumbnailLoaded(null);
|
||||||
if (thumbnails.isEmpty()) {
|
if (thumbnails.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// scale down the notification thumbnail for performance
|
// scale down the notification thumbnail for performance
|
||||||
PicassoHelper.loadScaledDownThumbnail(context, thumbnails)
|
final var target = new Target() {
|
||||||
.tag(PICASSO_PLAYER_THUMBNAIL_TAG)
|
@Override
|
||||||
.into(currentThumbnailTarget);
|
public void onError(@Nullable final Drawable error) {
|
||||||
}
|
Log.e(TAG, "Thumbnail - onError() called");
|
||||||
|
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
||||||
|
onThumbnailLoaded(null);
|
||||||
|
}
|
||||||
|
|
||||||
private void cancelLoadingCurrentThumbnail() {
|
@Override
|
||||||
// cancel the Picasso job associated with the player thumbnail, if any
|
public void onStart(@Nullable final Drawable placeholder) {
|
||||||
PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG);
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Thumbnail - onStart() called");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(@NonNull final Drawable result) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Thumbnail - onSuccess() called with: drawable = [" + result + "]");
|
||||||
|
}
|
||||||
|
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
||||||
|
onThumbnailLoaded(DrawableKt.toBitmapOrNull(result, result.getIntrinsicWidth(),
|
||||||
|
result.getIntrinsicHeight(), null));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CoilHelper.INSTANCE.loadScaledDownThumbnail(context, thumbnails, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
|
private void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
|
||||||
// Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the
|
// Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the
|
||||||
// thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since
|
// thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since
|
||||||
// onThumbnailLoaded won't be called twice with the same nonnull bitmap by Picasso's target.
|
// onThumbnailLoaded won't be called twice with the same nonnull bitmap by Coil's target.
|
||||||
if (currentThumbnail != bitmap) {
|
if (currentThumbnail != bitmap) {
|
||||||
currentThumbnail = bitmap;
|
currentThumbnail = bitmap;
|
||||||
UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap));
|
UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap));
|
||||||
|
|
|
@ -6,8 +6,8 @@ import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
public class PlayQueueItemBuilder {
|
public class PlayQueueItemBuilder {
|
||||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||||
|
@ -33,7 +33,7 @@ public class PlayQueueItemBuilder {
|
||||||
holder.itemDurationView.setVisibility(View.GONE);
|
holder.itemDurationView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView);
|
CoilHelper.INSTANCE.loadThumbnail(holder.itemThumbnailView, item.getThumbnails());
|
||||||
|
|
||||||
holder.itemRoot.setOnClickListener(view -> {
|
holder.itemRoot.setOnClickListener(view -> {
|
||||||
if (onItemClickListener != null) {
|
if (onItemClickListener != null) {
|
||||||
|
|
|
@ -13,8 +13,9 @@ import androidx.collection.SparseArrayCompat;
|
||||||
|
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.extractor.stream.Frameset;
|
import org.schabi.newpipe.extractor.stream.Frameset;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -177,8 +178,8 @@ public class SeekbarPreviewThumbnailHolder {
|
||||||
Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'");
|
Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'");
|
||||||
|
|
||||||
// Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient
|
// Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient
|
||||||
// Ensure that your are not running on the main-Thread this will otherwise hang
|
// Ensure that you are not running on the main thread, otherwise this will hang
|
||||||
final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
|
final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getApp(), url);
|
||||||
|
|
||||||
if (sw != null) {
|
if (sw != null) {
|
||||||
Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took "
|
Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took "
|
||||||
|
|
|
@ -13,10 +13,9 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
||||||
|
|
||||||
import java.io.IOException;
|
import coil.Coil;
|
||||||
|
|
||||||
public class ContentSettingsFragment extends BasePreferenceFragment {
|
public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
private String youtubeRestrictedModeEnabledKey;
|
private String youtubeRestrictedModeEnabledKey;
|
||||||
|
@ -42,14 +41,17 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
(preference, newValue) -> {
|
(preference, newValue) -> {
|
||||||
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
|
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
|
||||||
.fromPreferenceKey(requireContext(), (String) newValue));
|
.fromPreferenceKey(requireContext(), (String) newValue));
|
||||||
try {
|
final var loader = Coil.imageLoader(preference.getContext());
|
||||||
PicassoHelper.clearCache(preference.getContext());
|
if (loader.getMemoryCache() != null) {
|
||||||
Toast.makeText(preference.getContext(),
|
loader.getMemoryCache().clear();
|
||||||
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
} catch (final IOException e) {
|
|
||||||
Log.e(TAG, "Unable to clear Picasso cache", e);
|
|
||||||
}
|
}
|
||||||
|
if (loader.getDiskCache() != null) {
|
||||||
|
loader.getDiskCache().clear();
|
||||||
|
}
|
||||||
|
Toast.makeText(preference.getContext(),
|
||||||
|
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
|
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -25,8 +24,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||||
findPreference(getString(R.string.allow_heap_dumping_key));
|
findPreference(getString(R.string.allow_heap_dumping_key));
|
||||||
final Preference showMemoryLeaksPreference =
|
final Preference showMemoryLeaksPreference =
|
||||||
findPreference(getString(R.string.show_memory_leaks_key));
|
findPreference(getString(R.string.show_memory_leaks_key));
|
||||||
final Preference showImageIndicatorsPreference =
|
|
||||||
findPreference(getString(R.string.show_image_indicators_key));
|
|
||||||
final Preference checkNewStreamsPreference =
|
final Preference checkNewStreamsPreference =
|
||||||
findPreference(getString(R.string.check_new_streams_key));
|
findPreference(getString(R.string.check_new_streams_key));
|
||||||
final Preference crashTheAppPreference =
|
final Preference crashTheAppPreference =
|
||||||
|
@ -38,7 +35,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||||
|
|
||||||
assert allowHeapDumpingPreference != null;
|
assert allowHeapDumpingPreference != null;
|
||||||
assert showMemoryLeaksPreference != null;
|
assert showMemoryLeaksPreference != null;
|
||||||
assert showImageIndicatorsPreference != null;
|
|
||||||
assert checkNewStreamsPreference != null;
|
assert checkNewStreamsPreference != null;
|
||||||
assert crashTheAppPreference != null;
|
assert crashTheAppPreference != null;
|
||||||
assert showErrorSnackbarPreference != null;
|
assert showErrorSnackbarPreference != null;
|
||||||
|
@ -61,11 +57,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||||
showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available);
|
showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available);
|
||||||
}
|
}
|
||||||
|
|
||||||
showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
||||||
PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
checkNewStreamsPreference.setOnPreferenceClickListener(preference -> {
|
checkNewStreamsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
NotificationWorker.runNow(preference.getContext());
|
NotificationWorker.runNow(preference.getContext());
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -19,8 +19,8 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
@ -190,7 +190,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
final SubscriptionEntity entry = subscriptions.get(position);
|
final SubscriptionEntity entry = subscriptions.get(position);
|
||||||
holder.titleView.setText(entry.getName());
|
holder.titleView.setText(entry.getName());
|
||||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||||
PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView);
|
CoilHelper.INSTANCE.loadAvatar(holder.thumbnailView, entry.getAvatarUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
@ -154,20 +154,15 @@ public class SelectPlaylistFragment extends DialogFragment {
|
||||||
final int position) {
|
final int position) {
|
||||||
final PlaylistLocalItem selectedItem = playlists.get(position);
|
final PlaylistLocalItem selectedItem = playlists.get(position);
|
||||||
|
|
||||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
if (selectedItem instanceof PlaylistMetadataEntry entry) {
|
||||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
|
||||||
|
|
||||||
holder.titleView.setText(entry.name);
|
holder.titleView.setText(entry.name);
|
||||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||||
PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView);
|
CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, entry.thumbnailUrl);
|
||||||
|
} else if (selectedItem instanceof PlaylistRemoteEntity entry) {
|
||||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
|
||||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
|
||||||
|
|
||||||
holder.titleView.setText(entry.getName());
|
holder.titleView.setText(entry.getName());
|
||||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||||
PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl())
|
CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView,
|
||||||
.into(holder.thumbnailView);
|
entry.getThumbnailUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ import androidx.core.content.FileProvider;
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.Image;
|
import org.schabi.newpipe.extractor.Image;
|
||||||
|
import org.schabi.newpipe.util.image.CoilHelper;
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -278,7 +278,7 @@ public final class ShareUtils {
|
||||||
* @param content the content to share
|
* @param content the content to share
|
||||||
* @param images a set of possible {@link Image}s of the subject, among which to choose with
|
* @param images a set of possible {@link Image}s of the subject, among which to choose with
|
||||||
* {@link ImageStrategy#choosePreferredImage(List)} since that's likely to
|
* {@link ImageStrategy#choosePreferredImage(List)} since that's likely to
|
||||||
* provide an image that is in Picasso's cache
|
* provide an image that is in Coil's cache
|
||||||
*/
|
*/
|
||||||
public static void shareText(@NonNull final Context context,
|
public static void shareText(@NonNull final Context context,
|
||||||
@NonNull final String title,
|
@NonNull final String title,
|
||||||
|
@ -335,15 +335,7 @@ public final class ShareUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a {@link ClipData} with the image of the content shared, if it's in the app cache.
|
* Generate a {@link ClipData} with the image of the content shared.
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...)
|
|
||||||
* when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache}
|
|
||||||
* used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the
|
|
||||||
* thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null}
|
|
||||||
* will be returned.
|
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* In order to display the image in the content preview of the Android share sheet, an URI of
|
* In order to display the image in the content preview of the Android share sheet, an URI of
|
||||||
|
@ -359,9 +351,8 @@ public final class ShareUtils {
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
|
* This method will call {@link CoilHelper#loadBitmap(Context, String)} to get the
|
||||||
* thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
|
* thumbnail of the content.
|
||||||
* the Picasso library inside {@link PicassoHelper}.
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -378,7 +369,7 @@ public final class ShareUtils {
|
||||||
@NonNull final Context context,
|
@NonNull final Context context,
|
||||||
@NonNull final String thumbnailUrl) {
|
@NonNull final String thumbnailUrl) {
|
||||||
try {
|
try {
|
||||||
final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
|
final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(context, thumbnailUrl);
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
148
app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
Normal file
148
app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package org.schabi.newpipe.util.image
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.size.Size
|
||||||
|
import coil.target.Target
|
||||||
|
import coil.transform.Transformation
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.schabi.newpipe.MainActivity
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.extractor.Image
|
||||||
|
import org.schabi.newpipe.ktx.scale
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
object CoilHelper {
|
||||||
|
private val TAG = CoilHelper::class.java.simpleName
|
||||||
|
|
||||||
|
fun loadBitmapBlocking(context: Context, url: String?) = runBlocking {
|
||||||
|
loadBitmap(context, url, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadBitmap(
|
||||||
|
context: Context,
|
||||||
|
url: String?,
|
||||||
|
@DrawableRes placeholderResId: Int
|
||||||
|
): Bitmap? {
|
||||||
|
val request = getImageRequest(context, url, placeholderResId).build()
|
||||||
|
return context.imageLoader.execute(request).drawable?.toBitmapOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAvatar(target: ImageView, images: List<Image>) {
|
||||||
|
loadImageDefault(target, images, R.drawable.placeholder_person)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAvatar(target: ImageView, url: String?) {
|
||||||
|
loadImageDefault(target, url, R.drawable.placeholder_person)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadThumbnail(target: ImageView, images: List<Image>) {
|
||||||
|
loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadThumbnail(target: ImageView, url: String?) {
|
||||||
|
loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadScaledDownThumbnail(context: Context, images: List<Image>, target: Target) {
|
||||||
|
val url = ImageStrategy.choosePreferredImage(images)
|
||||||
|
val request = getImageRequest(context, url, R.drawable.placeholder_thumbnail_video)
|
||||||
|
.target(target)
|
||||||
|
.transformations(object : Transformation {
|
||||||
|
override val cacheKey = "COIL_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"
|
||||||
|
|
||||||
|
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "Thumbnail - transform() called")
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationThumbnailWidth = min(
|
||||||
|
context.resources.getDimension(R.dimen.player_notification_thumbnail_width),
|
||||||
|
input.width.toFloat()
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
var newHeight = input.height / (input.width / notificationThumbnailWidth)
|
||||||
|
val result = input.scale(notificationThumbnailWidth, newHeight)
|
||||||
|
|
||||||
|
if (result == input || !result.isMutable) {
|
||||||
|
// create a new mutable bitmap to prevent strange crashes on some
|
||||||
|
// devices (see #4638)
|
||||||
|
newHeight = input.height / (input.width / (notificationThumbnailWidth - 1))
|
||||||
|
val copied = input.scale(notificationThumbnailWidth, newHeight)
|
||||||
|
input.recycle()
|
||||||
|
return copied
|
||||||
|
} else {
|
||||||
|
input.recycle()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
context.imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadDetailsThumbnail(target: ImageView, images: List<Image>) {
|
||||||
|
val url = ImageStrategy.choosePreferredImage(images)
|
||||||
|
loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadBanner(target: ImageView, images: List<Image>) {
|
||||||
|
loadImageDefault(target, images, R.drawable.placeholder_channel_banner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadPlaylistThumbnail(target: ImageView, images: List<Image>) {
|
||||||
|
loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadPlaylistThumbnail(target: ImageView, url: String?) {
|
||||||
|
loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadImageDefault(
|
||||||
|
target: ImageView,
|
||||||
|
images: List<Image>,
|
||||||
|
@DrawableRes placeholderResId: Int
|
||||||
|
) {
|
||||||
|
loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadImageDefault(
|
||||||
|
target: ImageView,
|
||||||
|
url: String?,
|
||||||
|
@DrawableRes placeholderResId: Int,
|
||||||
|
showPlaceholder: Boolean = true
|
||||||
|
) {
|
||||||
|
val request = getImageRequest(target.context, url, placeholderResId, showPlaceholder)
|
||||||
|
.target(target)
|
||||||
|
.build()
|
||||||
|
target.context.imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImageRequest(
|
||||||
|
context: Context,
|
||||||
|
url: String?,
|
||||||
|
@DrawableRes placeholderResId: Int,
|
||||||
|
showPlaceholderWhileLoading: Boolean = true
|
||||||
|
): ImageRequest.Builder {
|
||||||
|
// if the URL was chosen with `choosePreferredImage` it will be null, but check again
|
||||||
|
// `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case
|
||||||
|
// for URLs stored in the database)
|
||||||
|
val takenUrl = url?.takeIf { it.isNotEmpty() && ImageStrategy.shouldLoadImages() }
|
||||||
|
|
||||||
|
return ImageRequest.Builder(context)
|
||||||
|
.data(takenUrl)
|
||||||
|
.error(placeholderResId)
|
||||||
|
.apply {
|
||||||
|
if (takenUrl != null || showPlaceholderWhileLoading) {
|
||||||
|
placeholder(placeholderResId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,224 +0,0 @@
|
||||||
package org.schabi.newpipe.util.image;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|
||||||
import static org.schabi.newpipe.util.image.ImageStrategy.choosePreferredImage;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.graphics.BitmapCompat;
|
|
||||||
|
|
||||||
import com.squareup.picasso.Cache;
|
|
||||||
import com.squareup.picasso.LruCache;
|
|
||||||
import com.squareup.picasso.OkHttp3Downloader;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
import com.squareup.picasso.RequestCreator;
|
|
||||||
import com.squareup.picasso.Transformation;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.extractor.Image;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public final class PicassoHelper {
|
|
||||||
private static final String TAG = PicassoHelper.class.getSimpleName();
|
|
||||||
private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY =
|
|
||||||
"PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY";
|
|
||||||
|
|
||||||
private PicassoHelper() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cache picassoCache;
|
|
||||||
private static OkHttpClient picassoDownloaderClient;
|
|
||||||
|
|
||||||
// suppress because terminate() is called in App.onTerminate(), preventing leaks
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private static Picasso picassoInstance;
|
|
||||||
|
|
||||||
|
|
||||||
public static void init(final Context context) {
|
|
||||||
picassoCache = new LruCache(10 * 1024 * 1024);
|
|
||||||
picassoDownloaderClient = new OkHttpClient.Builder()
|
|
||||||
.cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"),
|
|
||||||
50L * 1024L * 1024L))
|
|
||||||
// this should already be the default timeout in OkHttp3, but just to be sure...
|
|
||||||
.callTimeout(15, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
picassoInstance = new Picasso.Builder(context)
|
|
||||||
.memoryCache(picassoCache) // memory cache
|
|
||||||
.downloader(new OkHttp3Downloader(picassoDownloaderClient)) // disk cache
|
|
||||||
.defaultBitmapConfig(Bitmap.Config.RGB_565)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void terminate() {
|
|
||||||
picassoCache = null;
|
|
||||||
picassoDownloaderClient = null;
|
|
||||||
|
|
||||||
if (picassoInstance != null) {
|
|
||||||
picassoInstance.shutdown();
|
|
||||||
picassoInstance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearCache(final Context context) throws IOException {
|
|
||||||
picassoInstance.shutdown();
|
|
||||||
picassoCache.clear(); // clear memory cache
|
|
||||||
final okhttp3.Cache diskCache = picassoDownloaderClient.cache();
|
|
||||||
if (diskCache != null) {
|
|
||||||
diskCache.delete(); // clear disk cache
|
|
||||||
}
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cancelTag(final Object tag) {
|
|
||||||
picassoInstance.cancelTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setIndicatorsEnabled(final boolean enabled) {
|
|
||||||
picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static RequestCreator loadAvatar(@NonNull final List<Image> images) {
|
|
||||||
return loadImageDefault(images, R.drawable.placeholder_person);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadAvatar(@Nullable final String url) {
|
|
||||||
return loadImageDefault(url, R.drawable.placeholder_person);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadThumbnail(@NonNull final List<Image> images) {
|
|
||||||
return loadImageDefault(images, R.drawable.placeholder_thumbnail_video);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadThumbnail(@Nullable final String url) {
|
|
||||||
return loadImageDefault(url, R.drawable.placeholder_thumbnail_video);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadDetailsThumbnail(@NonNull final List<Image> images) {
|
|
||||||
return loadImageDefault(choosePreferredImage(images),
|
|
||||||
R.drawable.placeholder_thumbnail_video, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadBanner(@NonNull final List<Image> images) {
|
|
||||||
return loadImageDefault(images, R.drawable.placeholder_channel_banner);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadPlaylistThumbnail(@NonNull final List<Image> images) {
|
|
||||||
return loadImageDefault(images, R.drawable.placeholder_thumbnail_playlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadPlaylistThumbnail(@Nullable final String url) {
|
|
||||||
return loadImageDefault(url, R.drawable.placeholder_thumbnail_playlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadSeekbarThumbnailPreview(@Nullable final String url) {
|
|
||||||
return picassoInstance.load(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RequestCreator loadNotificationIcon(@Nullable final String url) {
|
|
||||||
return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static RequestCreator loadScaledDownThumbnail(final Context context,
|
|
||||||
@NonNull final List<Image> images) {
|
|
||||||
// scale down the notification thumbnail for performance
|
|
||||||
return PicassoHelper.loadThumbnail(images)
|
|
||||||
.transform(new Transformation() {
|
|
||||||
@Override
|
|
||||||
public Bitmap transform(final Bitmap source) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Thumbnail - transform() called");
|
|
||||||
}
|
|
||||||
|
|
||||||
final float notificationThumbnailWidth = Math.min(
|
|
||||||
context.getResources()
|
|
||||||
.getDimension(R.dimen.player_notification_thumbnail_width),
|
|
||||||
source.getWidth());
|
|
||||||
|
|
||||||
final Bitmap result = BitmapCompat.createScaledBitmap(
|
|
||||||
source,
|
|
||||||
(int) notificationThumbnailWidth,
|
|
||||||
(int) (source.getHeight()
|
|
||||||
/ (source.getWidth() / notificationThumbnailWidth)),
|
|
||||||
null,
|
|
||||||
true);
|
|
||||||
|
|
||||||
if (result == source || !result.isMutable()) {
|
|
||||||
// create a new mutable bitmap to prevent strange crashes on some
|
|
||||||
// devices (see #4638)
|
|
||||||
final Bitmap copied = BitmapCompat.createScaledBitmap(
|
|
||||||
source,
|
|
||||||
(int) notificationThumbnailWidth - 1,
|
|
||||||
(int) (source.getHeight() / (source.getWidth()
|
|
||||||
/ (notificationThumbnailWidth - 1))),
|
|
||||||
null,
|
|
||||||
true);
|
|
||||||
source.recycle();
|
|
||||||
return copied;
|
|
||||||
} else {
|
|
||||||
source.recycle();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return PLAYER_THUMBNAIL_TRANSFORMATION_KEY;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static Bitmap getImageFromCacheIfPresent(@NonNull final String imageUrl) {
|
|
||||||
// URLs in the internal cache finish with \n so we need to add \n to image URLs
|
|
||||||
return picassoCache.get(imageUrl + "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static RequestCreator loadImageDefault(@NonNull final List<Image> images,
|
|
||||||
@DrawableRes final int placeholderResId) {
|
|
||||||
return loadImageDefault(choosePreferredImage(images), placeholderResId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RequestCreator loadImageDefault(@Nullable final String url,
|
|
||||||
@DrawableRes final int placeholderResId) {
|
|
||||||
return loadImageDefault(url, placeholderResId, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RequestCreator loadImageDefault(@Nullable final String url,
|
|
||||||
@DrawableRes final int placeholderResId,
|
|
||||||
final boolean showPlaceholderWhileLoading) {
|
|
||||||
// if the URL was chosen with `choosePreferredImage` it will be null, but check again
|
|
||||||
// `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case
|
|
||||||
// for URLs stored in the database)
|
|
||||||
if (isNullOrEmpty(url) || !ImageStrategy.shouldLoadImages()) {
|
|
||||||
return picassoInstance
|
|
||||||
.load((String) null)
|
|
||||||
.placeholder(placeholderResId) // show placeholder when no image should load
|
|
||||||
.error(placeholderResId);
|
|
||||||
} else {
|
|
||||||
final RequestCreator requestCreator = picassoInstance
|
|
||||||
.load(url)
|
|
||||||
.error(placeholderResId);
|
|
||||||
if (showPlaceholderWhileLoading) {
|
|
||||||
requestCreator.placeholder(placeholderResId);
|
|
||||||
}
|
|
||||||
return requestCreator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -302,7 +302,6 @@
|
||||||
<item quantity="other">%s مشارك</item>
|
<item quantity="other">%s مشارك</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="loading_metadata_title">جلب البيانات الوصفية…</string>
|
<string name="loading_metadata_title">جلب البيانات الوصفية…</string>
|
||||||
<string name="show_image_indicators_title">إظهار مؤشرات الصور</string>
|
|
||||||
<string name="app_update_available_notification_text">انقر للتنزيل %s</string>
|
<string name="app_update_available_notification_text">انقر للتنزيل %s</string>
|
||||||
<string name="feed_use_dedicated_fetch_method_disable_button">تعطيل الوضع السريع</string>
|
<string name="feed_use_dedicated_fetch_method_disable_button">تعطيل الوضع السريع</string>
|
||||||
<string name="enumeration_comma">,</string>
|
<string name="enumeration_comma">,</string>
|
||||||
|
@ -830,7 +829,6 @@
|
||||||
<string name="you_successfully_subscribed">لقد اشتركت الآن في هذه القناة</string>
|
<string name="you_successfully_subscribed">لقد اشتركت الآن في هذه القناة</string>
|
||||||
<string name="downloads_storage_use_saf_summary_api_29">بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط</string>
|
<string name="downloads_storage_use_saf_summary_api_29">بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط</string>
|
||||||
<string name="generate_unique_name">إنشاء اسم فريد</string>
|
<string name="generate_unique_name">إنشاء اسم فريد</string>
|
||||||
<string name="show_image_indicators_summary">أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة</string>
|
|
||||||
<string name="error_ssl_exception">فشل الاتصال الآمن</string>
|
<string name="error_ssl_exception">فشل الاتصال الآمن</string>
|
||||||
<string name="youtube_music_premium_content">يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe.</string>
|
<string name="youtube_music_premium_content">يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe.</string>
|
||||||
<string name="previous_stream">البث السابق</string>
|
<string name="previous_stream">البث السابق</string>
|
||||||
|
|
|
@ -674,8 +674,6 @@
|
||||||
<string name="seekbar_preview_thumbnail_title">معاينة مصغرة على شريط التمرير</string>
|
<string name="seekbar_preview_thumbnail_title">معاينة مصغرة على شريط التمرير</string>
|
||||||
<string name="mark_as_watched">وضع علامة على تمت مشاهدته</string>
|
<string name="mark_as_watched">وضع علامة على تمت مشاهدته</string>
|
||||||
<string name="detail_heart_img_view_description">أُعجب بها منشئ المحتوى</string>
|
<string name="detail_heart_img_view_description">أُعجب بها منشئ المحتوى</string>
|
||||||
<string name="show_image_indicators_summary">أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة</string>
|
|
||||||
<string name="show_image_indicators_title">إظهار مؤشرات الصور</string>
|
|
||||||
<string name="remote_search_suggestions">اقتراحات البحث عن بعد</string>
|
<string name="remote_search_suggestions">اقتراحات البحث عن بعد</string>
|
||||||
<string name="local_search_suggestions">اقتراحات البحث المحلية</string>
|
<string name="local_search_suggestions">اقتراحات البحث المحلية</string>
|
||||||
<string name="main_page_content_swipe_remove">اسحب العناصر لإزالتها</string>
|
<string name="main_page_content_swipe_remove">اسحب العناصر لإزالتها</string>
|
||||||
|
|
|
@ -505,8 +505,6 @@
|
||||||
<string name="preferred_player_fetcher_notification_title">Məlumat əldə edilir…</string>
|
<string name="preferred_player_fetcher_notification_title">Məlumat əldə edilir…</string>
|
||||||
<string name="show_original_time_ago_title">Elementlərdə orijinal əvvəlki vaxtı göstər</string>
|
<string name="show_original_time_ago_title">Elementlərdə orijinal əvvəlki vaxtı göstər</string>
|
||||||
<string name="enable_disposed_exceptions_title">Yaşam dövrəsi xaricindəki xətaları bildir</string>
|
<string name="enable_disposed_exceptions_title">Yaşam dövrəsi xaricindəki xətaları bildir</string>
|
||||||
<string name="show_image_indicators_title">Şəkil göstəricilərini göstər</string>
|
|
||||||
<string name="show_image_indicators_summary">Şəkillərin üzərində mənbəsini göstərən Picasso rəngli lentləri göstər: şəbəkə üçün qırmızı, disk üçün mavi və yaddaş üçün yaşıl</string>
|
|
||||||
<string name="pause_downloads_on_mobile_desc">Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır</string>
|
<string name="pause_downloads_on_mobile_desc">Bəzi endirmələri dayandırmaq mümkün olmasa da, mobil dataya keçərkən faydalıdır</string>
|
||||||
<string name="close">Bağla</string>
|
<string name="close">Bağla</string>
|
||||||
<string name="error_progress_lost">Fayl silindiyi üçün irəliləyiş itirildi</string>
|
<string name="error_progress_lost">Fayl silindiyi üçün irəliləyiş itirildi</string>
|
||||||
|
|
|
@ -630,8 +630,6 @@
|
||||||
<string name="faq">Перайсці на вэбсайт</string>
|
<string name="faq">Перайсці на вэбсайт</string>
|
||||||
<string name="main_page_content_swipe_remove">Правядзіце пальцам па элементах, каб выдаліць іх</string>
|
<string name="main_page_content_swipe_remove">Правядзіце пальцам па элементах, каб выдаліць іх</string>
|
||||||
<string name="unset_playlist_thumbnail">Адмяніць пастаянную мініяцюру</string>
|
<string name="unset_playlist_thumbnail">Адмяніць пастаянную мініяцюру</string>
|
||||||
<string name="show_image_indicators_title">Паказаць індыкатары выявы</string>
|
|
||||||
<string name="show_image_indicators_summary">Паказваць каляровыя стужкі Пікаса на выявах, якія пазначаюць іх крыніцу: чырвоная для сеткі, сіняя для дыска і зялёная для памяці</string>
|
|
||||||
<string name="feed_processing_message">Апрацоўка стужкі…</string>
|
<string name="feed_processing_message">Апрацоўка стужкі…</string>
|
||||||
<string name="downloads_storage_ask_summary_no_saf_notice">Вам будзе прапанавана, дзе захоўваць кожную спампоўку</string>
|
<string name="downloads_storage_ask_summary_no_saf_notice">Вам будзе прапанавана, дзе захоўваць кожную спампоўку</string>
|
||||||
<string name="feed_notification_loading">Загрузка стужкі…</string>
|
<string name="feed_notification_loading">Загрузка стужкі…</string>
|
||||||
|
|
|
@ -539,7 +539,6 @@
|
||||||
<string name="restricted_video">Това видео е с възрастова граница.
|
<string name="restricted_video">Това видео е с възрастова граница.
|
||||||
\n
|
\n
|
||||||
\nВключете „%1$s“ в настройките ако искате да го пуснете.</string>
|
\nВключете „%1$s“ в настройките ако искате да го пуснете.</string>
|
||||||
<string name="show_image_indicators_summary">Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта)</string>
|
|
||||||
<string name="auto_device_theme_title">Автоматична (тази на устройството)</string>
|
<string name="auto_device_theme_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="select_a_playlist">Избете плейлист</string>
|
<string name="select_a_playlist">Избете плейлист</string>
|
||||||
|
|
|
@ -589,7 +589,6 @@
|
||||||
<string name="streams_notification_channel_name">নতুন ধারা</string>
|
<string name="streams_notification_channel_name">নতুন ধারা</string>
|
||||||
<string name="streams_notifications_interval_title">কম্পাঙ্ক দেখো</string>
|
<string name="streams_notifications_interval_title">কম্পাঙ্ক দেখো</string>
|
||||||
<string name="seekbar_preview_thumbnail_title">পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন</string>
|
<string name="seekbar_preview_thumbnail_title">পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন</string>
|
||||||
<string name="show_image_indicators_title">ছবিরূপ সূচক দেখাও</string>
|
|
||||||
<string name="dont_show">দেখিও না</string>
|
<string name="dont_show">দেখিও না</string>
|
||||||
<string name="any_network">যেকোনো নেটওয়ার্ক</string>
|
<string name="any_network">যেকোনো নেটওয়ার্ক</string>
|
||||||
<string name="enqueued_next">পরেরটা ক্রমে রাখা হয়েছে</string>
|
<string name="enqueued_next">পরেরটা ক্রমে রাখা হয়েছে</string>
|
||||||
|
|
|
@ -625,7 +625,6 @@
|
||||||
<string name="dont_show">No mostris</string>
|
<string name="dont_show">No mostris</string>
|
||||||
<string name="low_quality_smaller">Baixa qualitat (més petit)</string>
|
<string name="low_quality_smaller">Baixa qualitat (més petit)</string>
|
||||||
<string name="high_quality_larger">Alta qualitat (més gran)</string>
|
<string name="high_quality_larger">Alta qualitat (més gran)</string>
|
||||||
<string name="show_image_indicators_title">Mostra indicadors de la imatge</string>
|
|
||||||
<string name="disable_media_tunneling_summary">Desactiva l\'entunelament del contingut si en els videos hi ha una pantalla negre o tartamudegen</string>
|
<string name="disable_media_tunneling_summary">Desactiva l\'entunelament del contingut si en els videos hi ha una pantalla negre o tartamudegen</string>
|
||||||
<string name="show_channel_details">Mostra detalls del canal</string>
|
<string name="show_channel_details">Mostra detalls del canal</string>
|
||||||
<string name="no_dir_yet">No s\'ha establert una carpeta de descàrregues, selecciona la carpeta per defecte ara</string>
|
<string name="no_dir_yet">No s\'ha establert una carpeta de descàrregues, selecciona la carpeta per defecte ara</string>
|
||||||
|
@ -669,7 +668,6 @@
|
||||||
<string name="detail_pinned_comment_view_description">Comentari fixat</string>
|
<string name="detail_pinned_comment_view_description">Comentari fixat</string>
|
||||||
<string name="show_crash_the_player_title">Mostra \"Força el tancament del reproductor\"</string>
|
<string name="show_crash_the_player_title">Mostra \"Força el tancament del reproductor\"</string>
|
||||||
<string name="show_crash_the_player_summary">Mostra una opció de fallada quan s\'utilitza el reproductor</string>
|
<string name="show_crash_the_player_summary">Mostra una opció de fallada quan s\'utilitza el reproductor</string>
|
||||||
<string name="show_image_indicators_summary">Mostra les cintes de color Picasso a la part superior de les imatges que indiquen la seva font: vermell per a la xarxa, blau per al disc i verd per a la memòria</string>
|
|
||||||
<string name="leak_canary_not_available">El LeakCanary no està disponible</string>
|
<string name="leak_canary_not_available">El LeakCanary no està disponible</string>
|
||||||
<string name="streams_notifications_interval_title">Comprovant freqüència</string>
|
<string name="streams_notifications_interval_title">Comprovant freqüència</string>
|
||||||
<string name="streams_notifications_network_title">Es necesita una conexió a Internet</string>
|
<string name="streams_notifications_network_title">Es necesita una conexió a Internet</string>
|
||||||
|
|
|
@ -631,8 +631,6 @@
|
||||||
<string name="dont_show">پیشان نەدرێت</string>
|
<string name="dont_show">پیشان نەدرێت</string>
|
||||||
<string name="low_quality_smaller">کواڵێتی نزم (بچووکتر)</string>
|
<string name="low_quality_smaller">کواڵێتی نزم (بچووکتر)</string>
|
||||||
<string name="high_quality_larger">کواڵێتی بەرز (گەورەتر)</string>
|
<string name="high_quality_larger">کواڵێتی بەرز (گەورەتر)</string>
|
||||||
<string name="show_image_indicators_summary">پیشاندانی شریتە ڕەنگکراوەکانی پیکاسۆ لەسەرووی وێنەکانەوە بۆ بەدیار خستنی سەرچاوەکانیان : سوور بۆ تۆڕ ، شین بۆ دیسک و سەوز بۆ بیرگە</string>
|
|
||||||
<string name="show_image_indicators_title">پیشاندانی دیارخەرەکانی وێنە</string>
|
|
||||||
<string name="remote_search_suggestions">پێشنیازکراوەکانی گەڕانی ڕیمۆت</string>
|
<string name="remote_search_suggestions">پێشنیازکراوەکانی گەڕانی ڕیمۆت</string>
|
||||||
<string name="local_search_suggestions">پێشنیازکراوەکانی گەڕانی نێوخۆیی</string>
|
<string name="local_search_suggestions">پێشنیازکراوەکانی گەڕانی نێوخۆیی</string>
|
||||||
<string name="mark_as_watched">دیارکردن وەک بینراو</string>
|
<string name="mark_as_watched">دیارکردن وەک بینراو</string>
|
||||||
|
|
|
@ -654,8 +654,6 @@
|
||||||
<item quantity="few">%s stahování dokončena</item>
|
<item quantity="few">%s stahování dokončena</item>
|
||||||
<item quantity="other">%s stahováních dokončeno</item>
|
<item quantity="other">%s stahováních dokončeno</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="show_image_indicators_summary">Zobrazit barevné pásky Picasso na obrázcích označujících jejich zdroj: červená pro síť, modrá pro disk a zelená pro paměť</string>
|
|
||||||
<string name="show_image_indicators_title">Zobrazit indikátory obrázků</string>
|
|
||||||
<string name="remote_search_suggestions">Vzdálené návrhy vyhledávání</string>
|
<string name="remote_search_suggestions">Vzdálené návrhy vyhledávání</string>
|
||||||
<string name="local_search_suggestions">Lokální návrhy vyhledávání</string>
|
<string name="local_search_suggestions">Lokální návrhy vyhledávání</string>
|
||||||
<string name="start_main_player_fullscreen_summary">Pokud je vypnuté automatické otáčení, nespouštět video v mini přehrávači, ale přepnout se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky</string>
|
<string name="start_main_player_fullscreen_summary">Pokud je vypnuté automatické otáčení, nespouštět video v mini přehrávači, ale přepnout se přímo do režimu celé obrazovky. Do mini přehrávače se lze i nadále dostat ukončením režimu celé obrazovky</string>
|
||||||
|
|
|
@ -516,7 +516,6 @@
|
||||||
<string name="processing_may_take_a_moment">Behandler… Det kan tage et øjeblik</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="show_memory_leaks">Vis hukommelseslækager</string>
|
||||||
<string name="disable_media_tunneling_title">Deaktivér medietunneling</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="streams_notifications_network_title">Netværkskrav</string>
|
||||||
<string name="any_network">Alle netværk</string>
|
<string name="any_network">Alle netværk</string>
|
||||||
<string name="streams_notifications_interval_title">Kontrolfrekvens</string>
|
<string name="streams_notifications_interval_title">Kontrolfrekvens</string>
|
||||||
|
@ -695,7 +694,6 @@
|
||||||
<string name="faq_title">Ofte stillede spørgsmål</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_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å webside</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="app_update_unavailable_toast">Du kører den nyeste version af NewPipe</string>
|
||||||
<string name="new_seek_duration_toast">Pga. ExoPlayer-begrænsninger blev søgevarigheden 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="feed_group_show_only_ungrouped_subscriptions">Vis kun ikke-grupperede abonnementer</string>
|
||||||
|
|
|
@ -637,8 +637,6 @@
|
||||||
<string name="off">Aus</string>
|
<string name="off">Aus</string>
|
||||||
<string name="mark_as_watched">Als gesehen markieren</string>
|
<string name="mark_as_watched">Als gesehen markieren</string>
|
||||||
<string name="detail_heart_img_view_description">Vom Ersteller mit Herz versehen</string>
|
<string name="detail_heart_img_view_description">Vom Ersteller mit Herz versehen</string>
|
||||||
<string name="show_image_indicators_summary">Farbige Picasso-Bänder über den Bildern anzeigen, die deren Quelle angeben: rot für Netzwerk, blau für Festplatte und grün für Speicher</string>
|
|
||||||
<string name="show_image_indicators_title">Bildindikatoren anzeigen</string>
|
|
||||||
<string name="remote_search_suggestions">Entfernte Suchvorschläge</string>
|
<string name="remote_search_suggestions">Entfernte Suchvorschläge</string>
|
||||||
<string name="local_search_suggestions">Lokale Suchvorschläge</string>
|
<string name="local_search_suggestions">Lokale Suchvorschläge</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -634,8 +634,6 @@
|
||||||
<string name="seekbar_preview_thumbnail_title">Προεπισκόπηση στην μπάρα αναζήτησης</string>
|
<string name="seekbar_preview_thumbnail_title">Προεπισκόπηση στην μπάρα αναζήτησης</string>
|
||||||
<string name="mark_as_watched">Σήμανση ως αναπαραχθέν</string>
|
<string name="mark_as_watched">Σήμανση ως αναπαραχθέν</string>
|
||||||
<string name="detail_heart_img_view_description">Επισημάνθηκε από τον δημιουργό</string>
|
<string name="detail_heart_img_view_description">Επισημάνθηκε από τον δημιουργό</string>
|
||||||
<string name="show_image_indicators_summary">Εμφάνιση χρωματιστής κορδέλας πάνω στις εικόνες, που δείχνει την πηγή τους: κόκκινη για δίκτυο, μπλε για δίσκο και πράσινο για μνήμη</string>
|
|
||||||
<string name="show_image_indicators_title">Εμφάνιση δεικτών εικόνων</string>
|
|
||||||
<string name="remote_search_suggestions">Προτάσεις απομακρυσμένης αναζήτησης</string>
|
<string name="remote_search_suggestions">Προτάσεις απομακρυσμένης αναζήτησης</string>
|
||||||
<string name="local_search_suggestions">Προτάσεις τοπικής αναζήτησης</string>
|
<string name="local_search_suggestions">Προτάσεις τοπικής αναζήτησης</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -647,8 +647,6 @@
|
||||||
<string name="comments_are_disabled">Los comentarios están deshabilitados</string>
|
<string name="comments_are_disabled">Los comentarios están deshabilitados</string>
|
||||||
<string name="detail_heart_img_view_description">De corazón por el creador</string>
|
<string name="detail_heart_img_view_description">De corazón por el creador</string>
|
||||||
<string name="mark_as_watched">Marcar como visto</string>
|
<string name="mark_as_watched">Marcar como visto</string>
|
||||||
<string name="show_image_indicators_summary">Mostrar cintas de colores Picasso encima de las imágenes indicando su origen: rojo para la red, azul para el disco y verde para la memoria</string>
|
|
||||||
<string name="show_image_indicators_title">Mostrar indicadores de imagen</string>
|
|
||||||
<string name="remote_search_suggestions">Sugerencias de búsqueda remota</string>
|
<string name="remote_search_suggestions">Sugerencias de búsqueda remota</string>
|
||||||
<string name="local_search_suggestions">Sugerencias de búsqueda local</string>
|
<string name="local_search_suggestions">Sugerencias de búsqueda local</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -634,8 +634,6 @@
|
||||||
\n
|
\n
|
||||||
\nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
|
\nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
|
||||||
<string name="mark_as_watched">Märgi vaadatuks</string>
|
<string name="mark_as_watched">Märgi vaadatuks</string>
|
||||||
<string name="show_image_indicators_summary">Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu</string>
|
|
||||||
<string name="show_image_indicators_title">Näita piltide allikat</string>
|
|
||||||
<string name="remote_search_suggestions">Kaugotsingu soovitused</string>
|
<string name="remote_search_suggestions">Kaugotsingu soovitused</string>
|
||||||
<string name="local_search_suggestions">Kohaliku otsingu soovitused</string>
|
<string name="local_search_suggestions">Kohaliku otsingu soovitused</string>
|
||||||
<string name="main_page_content_swipe_remove">Üksuse eemaldamiseks viipa</string>
|
<string name="main_page_content_swipe_remove">Üksuse eemaldamiseks viipa</string>
|
||||||
|
|
|
@ -641,8 +641,6 @@
|
||||||
<item quantity="one">Deskarga amaituta</item>
|
<item quantity="one">Deskarga amaituta</item>
|
||||||
<item quantity="other">%s Deskarga amaituta</item>
|
<item quantity="other">%s Deskarga amaituta</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="show_image_indicators_summary">Irudien gainean Picasso koloretako zintak erakutsi, jatorria adieraziz: gorria sarerako, urdina diskorako eta berdea memoriarako</string>
|
|
||||||
<string name="show_image_indicators_title">Erakutsi irudi-adierazleak</string>
|
|
||||||
<string name="remote_search_suggestions">Urruneko bilaketaren iradokizunak</string>
|
<string name="remote_search_suggestions">Urruneko bilaketaren iradokizunak</string>
|
||||||
<string name="local_search_suggestions">Tokiko bilaketa-iradokizunak</string>
|
<string name="local_search_suggestions">Tokiko bilaketa-iradokizunak</string>
|
||||||
<string name="mark_as_watched">Ikusi gisa markatu</string>
|
<string name="mark_as_watched">Ikusi gisa markatu</string>
|
||||||
|
|
|
@ -597,7 +597,6 @@
|
||||||
<string name="notification_colorize_title">رنگی کردن آگاهی</string>
|
<string name="notification_colorize_title">رنگی کردن آگاهی</string>
|
||||||
<string name="open_with">گشودن با</string>
|
<string name="open_with">گشودن با</string>
|
||||||
<string name="mark_as_watched">نشانه به عنوان دیده شده</string>
|
<string name="mark_as_watched">نشانه به عنوان دیده شده</string>
|
||||||
<string name="show_image_indicators_summary">نمایش روبانهای رنگی پیکاسو در بالای تصویرها کهنشانگر منبعشان است: قرمز برای شبکه ، آبی برای دیسک و سبز برای حافظه</string>
|
|
||||||
<string name="notification_colorize_summary">درخواست از اندروید برای سفارشیسازی رنگ آگاهی براساس رنگ اصلی در بندانگشتی (توجّه داشته باشید که روی همهٔ افزارهها در دسترس نیست)</string>
|
<string name="notification_colorize_summary">درخواست از اندروید برای سفارشیسازی رنگ آگاهی براساس رنگ اصلی در بندانگشتی (توجّه داشته باشید که روی همهٔ افزارهها در دسترس نیست)</string>
|
||||||
<string name="restricted_video">این ویدیو محدود به سن است.
|
<string name="restricted_video">این ویدیو محدود به سن است.
|
||||||
\n
|
\n
|
||||||
|
@ -635,7 +634,6 @@
|
||||||
<string name="detail_heart_img_view_description">قلبشده به دست ایجادگر</string>
|
<string name="detail_heart_img_view_description">قلبشده به دست ایجادگر</string>
|
||||||
<string name="local_search_suggestions">پیشنهادهای جستوجوی محلّی</string>
|
<string name="local_search_suggestions">پیشنهادهای جستوجوی محلّی</string>
|
||||||
<string name="remote_search_suggestions">پیشنهادهای جستوجوی دوردست</string>
|
<string name="remote_search_suggestions">پیشنهادهای جستوجوی دوردست</string>
|
||||||
<string name="show_image_indicators_title">نمایش نشانگرهای تصویر</string>
|
|
||||||
<plurals name="download_finished_notification">
|
<plurals name="download_finished_notification">
|
||||||
<item quantity="one">بارگیری پایان یافت</item>
|
<item quantity="one">بارگیری پایان یافت</item>
|
||||||
<item quantity="other">%s بارگیری پایان یافتند</item>
|
<item quantity="other">%s بارگیری پایان یافتند</item>
|
||||||
|
|
|
@ -634,8 +634,6 @@
|
||||||
<string name="no_dir_yet">Latauskansiota ei vielä asetettu, valitse ensin oletuslatauskansio</string>
|
<string name="no_dir_yet">Latauskansiota ei vielä asetettu, valitse ensin oletuslatauskansio</string>
|
||||||
<string name="comments_are_disabled">Kommentit poistettu käytöstä</string>
|
<string name="comments_are_disabled">Kommentit poistettu käytöstä</string>
|
||||||
<string name="mark_as_watched">Merkitse katsotuksi</string>
|
<string name="mark_as_watched">Merkitse katsotuksi</string>
|
||||||
<string name="show_image_indicators_summary">Näytä Picasso-värjätyt nauhat kuvien päällä osoittaakseen lähteen: punainen tarkoittaa verkkoa, sininen tarkoittaa levytilaa ja vihreä tarkoittaa muistia</string>
|
|
||||||
<string name="show_image_indicators_title">Näytä kuvailmaisimet</string>
|
|
||||||
<string name="remote_search_suggestions">Etähakuehdotukset</string>
|
<string name="remote_search_suggestions">Etähakuehdotukset</string>
|
||||||
<string name="local_search_suggestions">Paikalliset hakuehdotukset</string>
|
<string name="local_search_suggestions">Paikalliset hakuehdotukset</string>
|
||||||
<string name="enqueue_next_stream">Lisää seuraavaksi</string>
|
<string name="enqueue_next_stream">Lisää seuraavaksi</string>
|
||||||
|
|
|
@ -646,10 +646,8 @@
|
||||||
<string name="seekbar_preview_thumbnail_title">Prévisualisation de la barre de progression sur la miniature</string>
|
<string name="seekbar_preview_thumbnail_title">Prévisualisation de la barre de progression sur la miniature</string>
|
||||||
<string name="mark_as_watched">Marquer comme visionné</string>
|
<string name="mark_as_watched">Marquer comme visionné</string>
|
||||||
<string name="detail_heart_img_view_description">Apprécié par le créateur</string>
|
<string name="detail_heart_img_view_description">Apprécié par le créateur</string>
|
||||||
<string name="show_image_indicators_title">Afficher les indicateurs d’image</string>
|
|
||||||
<string name="remote_search_suggestions">Suggestions de recherche distante</string>
|
<string name="remote_search_suggestions">Suggestions de recherche distante</string>
|
||||||
<string name="local_search_suggestions">Suggestions de recherche locale</string>
|
<string name="local_search_suggestions">Suggestions de recherche locale</string>
|
||||||
<string name="show_image_indicators_summary">Affiche les rubans colorés de Picasso au-dessus des images indiquant leur source : rouge pour le réseau, bleu pour le disque et vert pour la mémoire</string>
|
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
<item quantity="one">%1$s téléchargement supprimé</item>
|
<item quantity="one">%1$s téléchargement supprimé</item>
|
||||||
<item quantity="many">%1$s téléchargements supprimés</item>
|
<item quantity="many">%1$s téléchargements supprimés</item>
|
||||||
|
|
|
@ -627,7 +627,6 @@
|
||||||
<item quantity="other">%s descargas finalizadas</item>
|
<item quantity="other">%s descargas finalizadas</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="seekbar_preview_thumbnail_title">Miniatura na barra de busca</string>
|
<string name="seekbar_preview_thumbnail_title">Miniatura na barra de busca</string>
|
||||||
<string name="show_image_indicators_title">Mostrar indicadores de imaxe</string>
|
|
||||||
<string name="disable_media_tunneling_summary">Desactive o túnel multimedia se experimentar unha pantalla en negro ou interrupcións na reprodución.</string>
|
<string name="disable_media_tunneling_summary">Desactive o túnel multimedia se experimentar unha pantalla en negro ou interrupcións na reprodución.</string>
|
||||||
<string name="disable_media_tunneling_title">Desactivar túnel multimedia</string>
|
<string name="disable_media_tunneling_title">Desactivar túnel multimedia</string>
|
||||||
<string name="enqueued">Engadido á cola</string>
|
<string name="enqueued">Engadido á cola</string>
|
||||||
|
@ -664,7 +663,6 @@
|
||||||
<string name="downloads_storage_use_saf_summary_api_29">A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado</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>
|
||||||
<string name="processing_may_take_a_moment">Procesando... Pode devagar un momento</string>
|
<string name="processing_may_take_a_moment">Procesando... Pode devagar un momento</string>
|
||||||
<string name="create_error_notification">Crear unha notificación de erro</string>
|
<string name="create_error_notification">Crear unha notificación de erro</string>
|
||||||
<string name="show_image_indicators_summary">Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria</string>
|
|
||||||
<string name="feed_new_items">Novos elementos</string>
|
<string name="feed_new_items">Novos elementos</string>
|
||||||
<string name="progressive_load_interval_exoplayer_default">Predefinido do ExoPlayer</string>
|
<string name="progressive_load_interval_exoplayer_default">Predefinido do ExoPlayer</string>
|
||||||
<string name="show_crash_the_player_title">Amosar \"Travar o reprodutor\"</string>
|
<string name="show_crash_the_player_title">Amosar \"Travar o reprodutor\"</string>
|
||||||
|
|
|
@ -654,8 +654,6 @@
|
||||||
<string name="seekbar_preview_thumbnail_title">תמונה מוקטנת בסרגל הנגינה</string>
|
<string name="seekbar_preview_thumbnail_title">תמונה מוקטנת בסרגל הנגינה</string>
|
||||||
<string name="detail_heart_img_view_description">סומן בלב על ידי היוצר</string>
|
<string name="detail_heart_img_view_description">סומן בלב על ידי היוצר</string>
|
||||||
<string name="mark_as_watched">סימון כנצפה</string>
|
<string name="mark_as_watched">סימון כנצפה</string>
|
||||||
<string name="show_image_indicators_summary">הצגת סרטים בסגנון פיקאסו בראש התמונות לציון המקור שלהם: אדום זה מהרשת, כחול מהכונן וירוק מהזיכרון</string>
|
|
||||||
<string name="show_image_indicators_title">הצגת מחווני תמונות</string>
|
|
||||||
<string name="remote_search_suggestions">הצעות חיפוש מרוחקות</string>
|
<string name="remote_search_suggestions">הצעות חיפוש מרוחקות</string>
|
||||||
<string name="local_search_suggestions">הצעות חיפוש מקומיות</string>
|
<string name="local_search_suggestions">הצעות חיפוש מקומיות</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -570,7 +570,6 @@
|
||||||
<string name="enqueued">कतारबद्ध हुआ</string>
|
<string name="enqueued">कतारबद्ध हुआ</string>
|
||||||
<string name="loading_stream_details">स्ट्रीम विवरण लोड हो रहे हैं…</string>
|
<string name="loading_stream_details">स्ट्रीम विवरण लोड हो रहे हैं…</string>
|
||||||
<string name="processing_may_take_a_moment">प्रोसेस हो रहा है… कुछ समय लग सकता है</string>
|
<string name="processing_may_take_a_moment">प्रोसेस हो रहा है… कुछ समय लग सकता है</string>
|
||||||
<string name="show_image_indicators_title">छवि संकेतक दिखाएं</string>
|
|
||||||
<string name="show_crash_the_player_summary">प्लेयर का उपयोग करते समय क्रैश विकल्प दिखाता है</string>
|
<string name="show_crash_the_player_summary">प्लेयर का उपयोग करते समय क्रैश विकल्प दिखाता है</string>
|
||||||
<string name="check_new_streams">नई स्ट्रीमों के लिए जांच चलाएं</string>
|
<string name="check_new_streams">नई स्ट्रीमों के लिए जांच चलाएं</string>
|
||||||
<string name="show_error_snackbar">एक त्रुटि स्नैकबार दिखाएं</string>
|
<string name="show_error_snackbar">एक त्रुटि स्नैकबार दिखाएं</string>
|
||||||
|
@ -668,7 +667,6 @@
|
||||||
<string name="leak_canary_not_available">लीक-कैनरी उपलब्ध नहीं है</string>
|
<string name="leak_canary_not_available">लीक-कैनरी उपलब्ध नहीं है</string>
|
||||||
<string name="error_report_notification_toast">एक त्रुटी हुई है, नोटीफिकेशन देखें</string>
|
<string name="error_report_notification_toast">एक त्रुटी हुई है, नोटीफिकेशन देखें</string>
|
||||||
<string name="disable_media_tunneling_summary">यदि वीडियो प्लेबैक पर आप काली स्क्रीन या रुक-रुक कर वीडियो चलने का अनुभव करते हैं तो मीडिया टनलिंग को अक्षम करें।</string>
|
<string name="disable_media_tunneling_summary">यदि वीडियो प्लेबैक पर आप काली स्क्रीन या रुक-रुक कर वीडियो चलने का अनुभव करते हैं तो मीडिया टनलिंग को अक्षम करें।</string>
|
||||||
<string name="show_image_indicators_summary">छवियों के शीर्ष पर पिकासो रंगीन रिबन दिखाएँ जो उनके स्रोत को दर्शाता है: नेटवर्क के लिए लाल, डिस्क के लिए नीला और मेमोरी के लिए हरा</string>
|
|
||||||
<string name="create_error_notification">त्रुटी की नोटीफिकेशन बनाएं</string>
|
<string name="create_error_notification">त्रुटी की नोटीफिकेशन बनाएं</string>
|
||||||
<string name="error_download_resource_gone">इस डाउनलोड को पुनर्प्राप्त नहीं किया जा सकता</string>
|
<string name="error_download_resource_gone">इस डाउनलोड को पुनर्प्राप्त नहीं किया जा सकता</string>
|
||||||
<string name="no_dir_yet">अभी तक कोई डाउनलोड फ़ोल्डर सेट नहीं किया गया है, अब डिफ़ॉल्ट डाउनलोड फ़ोल्डर चुनें</string>
|
<string name="no_dir_yet">अभी तक कोई डाउनलोड फ़ोल्डर सेट नहीं किया गया है, अब डिफ़ॉल्ट डाउनलोड फ़ोल्डर चुनें</string>
|
||||||
|
|
|
@ -646,7 +646,6 @@
|
||||||
<string name="service_provides_reason">%s pruža ovaj razlog:</string>
|
<string name="service_provides_reason">%s pruža ovaj razlog:</string>
|
||||||
<string name="processing_may_take_a_moment">Obrada u tijeku … Može malo potrajati</string>
|
<string name="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="main_page_content_swipe_remove">Za ukljanjanje stavki povuci ih</string>
|
||||||
<string name="show_image_indicators_title">Prikaži indikatore slike</string>
|
|
||||||
<plurals name="download_finished_notification">
|
<plurals name="download_finished_notification">
|
||||||
<item quantity="one">Preuzimanje je gotovo</item>
|
<item quantity="one">Preuzimanje je gotovo</item>
|
||||||
<item quantity="few">%s preuzimanja su gotova</item>
|
<item quantity="few">%s preuzimanja su gotova</item>
|
||||||
|
@ -655,7 +654,6 @@
|
||||||
<string name="start_main_player_fullscreen_title">Pokreni glavni player u cjeloekranskom prikazu</string>
|
<string name="start_main_player_fullscreen_title">Pokreni glavni player u cjeloekranskom prikazu</string>
|
||||||
<string name="enqueue_next_stream">Dodaj u popis kao sljedeći</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="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">
|
<plurals name="deleted_downloads_toast">
|
||||||
<item quantity="one">Izbrisano %1$s preuzimanje</item>
|
<item quantity="one">Izbrisano %1$s preuzimanje</item>
|
||||||
<item quantity="few">Izbrisana %1$s preuzimanja</item>
|
<item quantity="few">Izbrisana %1$s preuzimanja</item>
|
||||||
|
|
|
@ -570,7 +570,6 @@
|
||||||
<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="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="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="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>
|
<string name="choose_instance_prompt">Válasszon egy példányt</string>
|
||||||
<string name="feed_oldest_subscription_update">Lista legutóbbi frissítése: %s</string>
|
<string name="feed_oldest_subscription_update">Lista legutóbbi frissítése: %s</string>
|
||||||
|
@ -657,7 +656,6 @@
|
||||||
\nBiztos benne\? Ez nem vonható vissza!</string>
|
\nBiztos benne\? Ez nem vonható vissza!</string>
|
||||||
<string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string>
|
<string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string>
|
||||||
<string name="crash_the_player">Lejátszó összeomlasztása</string>
|
<string name="crash_the_player">Lejátszó összeomlasztása</string>
|
||||||
<string name="show_image_indicators_title">Képjelölők megjelenítése</string>
|
|
||||||
<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_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="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="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string>
|
||||||
|
|
|
@ -631,13 +631,11 @@
|
||||||
<string name="detail_heart_img_view_description">Disukai oleh kreator</string>
|
<string name="detail_heart_img_view_description">Disukai oleh kreator</string>
|
||||||
<string name="local_search_suggestions">Saran pencarian lokal</string>
|
<string name="local_search_suggestions">Saran pencarian lokal</string>
|
||||||
<string name="remote_search_suggestions">Saran pencarian remote</string>
|
<string name="remote_search_suggestions">Saran pencarian remote</string>
|
||||||
<string name="show_image_indicators_title">Tampilkan indikator gambar</string>
|
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
<item quantity="other">Menghapus %1$s unduhan</item>
|
<item quantity="other">Menghapus %1$s unduhan</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="enqueue_next_stream">tambahkan ke selanjutnya</string>
|
<string name="enqueue_next_stream">tambahkan ke selanjutnya</string>
|
||||||
<string name="enqueued_next">telah ditambahkan ke selanjutnya</string>
|
<string name="enqueued_next">telah ditambahkan ke selanjutnya</string>
|
||||||
<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="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="processing_may_take_a_moment">Memproses… Mungkin butuh waktu sebentar</string>
|
||||||
<string name="check_for_updates">Periksa Pembaruan</string>
|
<string name="check_for_updates">Periksa Pembaruan</string>
|
||||||
|
|
|
@ -657,8 +657,6 @@
|
||||||
<string name="show_original_time_ago_summary">Upprunalegir textar frá þjónustu verða sýnilegir í atriðum</string>
|
<string name="show_original_time_ago_summary">Upprunalegir textar frá þjónustu verða sýnilegir í atriðum</string>
|
||||||
<string name="disable_media_tunneling_title">Slökkva á fjölmiðlagöngum</string>
|
<string name="disable_media_tunneling_title">Slökkva á fjölmiðlagöngum</string>
|
||||||
<string name="disable_media_tunneling_summary">Slökktu á fjölmiðlagöngum ef þú finnur fyrir svörtum skjá eða stami við spilun myndbandar</string>
|
<string name="disable_media_tunneling_summary">Slökktu á fjölmiðlagöngum ef þú finnur fyrir svörtum skjá eða stami við spilun myndbandar</string>
|
||||||
<string name="show_image_indicators_title">Sýna myndvísa</string>
|
|
||||||
<string name="show_image_indicators_summary">Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni</string>
|
|
||||||
<string name="show_crash_the_player_title">Sýna „Hrynja spilara“</string>
|
<string name="show_crash_the_player_title">Sýna „Hrynja spilara“</string>
|
||||||
<string name="show_crash_the_player_summary">Sýna valkost til að hrynja spilara</string>
|
<string name="show_crash_the_player_summary">Sýna valkost til að hrynja spilara</string>
|
||||||
<string name="crash_the_app">Hrynja forrit</string>
|
<string name="crash_the_app">Hrynja forrit</string>
|
||||||
|
|
|
@ -644,8 +644,6 @@
|
||||||
<string name="comments_are_disabled">Commenti disattivati</string>
|
<string name="comments_are_disabled">Commenti disattivati</string>
|
||||||
<string name="detail_heart_img_view_description">Apprezzato dall\'autore</string>
|
<string name="detail_heart_img_view_description">Apprezzato dall\'autore</string>
|
||||||
<string name="mark_as_watched">Segna come visto</string>
|
<string name="mark_as_watched">Segna come visto</string>
|
||||||
<string name="show_image_indicators_summary">Mostra gli indicatori colorati Picasso sopra le immagini, per indicare la loro fonte: rosso per la rete, blu per il disco e verde per la memoria</string>
|
|
||||||
<string name="show_image_indicators_title">Mostra indicatori immagine</string>
|
|
||||||
<string name="remote_search_suggestions">Suggerimenti di ricerca remoti</string>
|
<string name="remote_search_suggestions">Suggerimenti di ricerca remoti</string>
|
||||||
<string name="local_search_suggestions">Suggerimenti di ricerca locali</string>
|
<string name="local_search_suggestions">Suggerimenti di ricerca locali</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -634,8 +634,6 @@
|
||||||
<plurals name="download_finished_notification">
|
<plurals name="download_finished_notification">
|
||||||
<item quantity="other">%s つのダウンロードが完了しました</item>
|
<item quantity="other">%s つのダウンロードが完了しました</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="show_image_indicators_summary">ピカソは、画像の上に、画像の出所を識別する色彩記章を表示します: 赤はネットワーク、青はディスク、緑はメモリ</string>
|
|
||||||
<string name="show_image_indicators_title">画像に標識を表示</string>
|
|
||||||
<string name="processing_may_take_a_moment">処理中… 少し時間がかかるかもしれません</string>
|
<string name="processing_may_take_a_moment">処理中… 少し時間がかかるかもしれません</string>
|
||||||
<string name="manual_update_description">新しいバージョンを手動で確認します</string>
|
<string name="manual_update_description">新しいバージョンを手動で確認します</string>
|
||||||
<string name="checking_updates_toast">アップデートを確認中…</string>
|
<string name="checking_updates_toast">アップデートを確認中…</string>
|
||||||
|
|
|
@ -384,7 +384,6 @@
|
||||||
<string name="show_original_time_ago_summary">ორიგინალური ტექსტები სერვისებიდან ხილული იქნება ნაკადის ერთეულებში</string>
|
<string name="show_original_time_ago_summary">ორიგინალური ტექსტები სერვისებიდან ხილული იქნება ნაკადის ერთეულებში</string>
|
||||||
<string name="disable_media_tunneling_title">მედია გვირაბის გათიშვა</string>
|
<string name="disable_media_tunneling_title">მედია გვირაბის გათიშვა</string>
|
||||||
<string name="disable_media_tunneling_summary">გამორთეთ მედია გვირაბი, თუ ვიდეოს დაკვრისას შავი ეკრანი ან ჭუჭყი გაქვთ</string>
|
<string name="disable_media_tunneling_summary">გამორთეთ მედია გვირაბი, თუ ვიდეოს დაკვრისას შავი ეკრანი ან ჭუჭყი გაქვთ</string>
|
||||||
<string name="show_image_indicators_title">გამოსახულების ინდიკატორების ჩვენება</string>
|
|
||||||
<string name="show_crash_the_player_title">აჩვენე \"დამკვრელის დამსხვრევა\"</string>
|
<string name="show_crash_the_player_title">აჩვენე \"დამკვრელის დამსხვრევა\"</string>
|
||||||
<string name="show_crash_the_player_summary">აჩვენებს ავარიის ვარიანტს დამკვრელის გამოყენებისას</string>
|
<string name="show_crash_the_player_summary">აჩვენებს ავარიის ვარიანტს დამკვრელის გამოყენებისას</string>
|
||||||
<string name="check_new_streams">გაუშვით შემოწმება ახალი ნაკადებისთვის</string>
|
<string name="check_new_streams">გაუშვით შემოწმება ახალი ნაკადებისთვის</string>
|
||||||
|
@ -683,7 +682,6 @@
|
||||||
<string name="subscriptions_import_unsuccessful">გამოწერების იმპორტი ვერ მოხერხდა</string>
|
<string name="subscriptions_import_unsuccessful">გამოწერების იმპორტი ვერ მოხერხდა</string>
|
||||||
<string name="enable_disposed_exceptions_title">შეატყობინეთ სასიცოცხლო ციკლის შეცდომებს</string>
|
<string name="enable_disposed_exceptions_title">შეატყობინეთ სასიცოცხლო ციკლის შეცდომებს</string>
|
||||||
<string name="enable_disposed_exceptions_summary">იძულებითი მოხსენება შეუსაბამო Rx გამონაკლისების შესახებ ფრაგმენტის ან აქტივობის სასიცოცხლო ციკლის გარეთ განკარგვის შემდეგ</string>
|
<string name="enable_disposed_exceptions_summary">იძულებითი მოხსენება შეუსაბამო Rx გამონაკლისების შესახებ ფრაგმენტის ან აქტივობის სასიცოცხლო ციკლის გარეთ განკარგვის შემდეგ</string>
|
||||||
<string name="show_image_indicators_summary">აჩვენეთ პიკასოს ფერადი ლენტები სურათების თავზე, სადაც მითითებულია მათი წყარო: წითელი ქსელისთვის, ლურჯი დისკისთვის და მწვანე მეხსიერებისთვის</string>
|
|
||||||
<string name="feed_load_error_fast_unknown">სწრაფი კვების რეჟიმი ამაზე მეტ ინფორმაციას არ იძლევა.</string>
|
<string name="feed_load_error_fast_unknown">სწრაფი კვების რეჟიმი ამაზე მეტ ინფორმაციას არ იძლევა.</string>
|
||||||
<string name="feed_load_error_account_info">„%s“-ის არხის ჩატვირთვა ვერ მოხერხდა.</string>
|
<string name="feed_load_error_account_info">„%s“-ის არხის ჩატვირთვა ვერ მოხერხდა.</string>
|
||||||
<string name="feed_use_dedicated_fetch_method_summary">ხელმისაწვდომია ზოგიერთ სერვისში, როგორც წესი, ბევრად უფრო სწრაფია, მაგრამ შეიძლება დააბრუნოს შეზღუდული რაოდენობის ელემენტი და ხშირად არასრული ინფორმაცია (მაგ. ხანგრძლივობის გარეშე, ელემენტის ტიპი, არ არის ლაივის სტატუსი)</string>
|
<string name="feed_use_dedicated_fetch_method_summary">ხელმისაწვდომია ზოგიერთ სერვისში, როგორც წესი, ბევრად უფრო სწრაფია, მაგრამ შეიძლება დააბრუნოს შეზღუდული რაოდენობის ელემენტი და ხშირად არასრული ინფორმაცია (მაგ. ხანგრძლივობის გარეშე, ელემენტის ტიპი, არ არის ლაივის სტატუსი)</string>
|
||||||
|
|
|
@ -545,8 +545,6 @@
|
||||||
<string name="disable_media_tunneling_title">미디어 터널링 비활성화</string>
|
<string name="disable_media_tunneling_title">미디어 터널링 비활성화</string>
|
||||||
<string name="show_original_time_ago_summary">서비스의 원본 텍스트가 스트림 항목에 표시됩니다</string>
|
<string name="show_original_time_ago_summary">서비스의 원본 텍스트가 스트림 항목에 표시됩니다</string>
|
||||||
<string name="disable_media_tunneling_summary">동영상 재생 시 검은 화면이 나타나거나 끊김 현상이 발생하면 미디어 터널링을 비활성화하세요.</string>
|
<string name="disable_media_tunneling_summary">동영상 재생 시 검은 화면이 나타나거나 끊김 현상이 발생하면 미디어 터널링을 비활성화하세요.</string>
|
||||||
<string name="show_image_indicators_title">이미지 표시기 표시</string>
|
|
||||||
<string name="show_image_indicators_summary">원본을 나타내는 이미지 위에 피카소 컬러 리본 표시: 네트워크는 빨간색, 디스크는 파란색, 메모리는 녹색</string>
|
|
||||||
<string name="show_crash_the_player_title">\"플레이어 충돌\" 표시</string>
|
<string name="show_crash_the_player_title">\"플레이어 충돌\" 표시</string>
|
||||||
<string name="show_crash_the_player_summary">플레이어를 사용할 때 충돌 옵션을 표시합니다</string>
|
<string name="show_crash_the_player_summary">플레이어를 사용할 때 충돌 옵션을 표시합니다</string>
|
||||||
<string name="check_new_streams">새로운 스트림 확인 실행</string>
|
<string name="check_new_streams">새로운 스트림 확인 실행</string>
|
||||||
|
|
|
@ -644,8 +644,6 @@
|
||||||
<string name="dont_show">Nerodyti</string>
|
<string name="dont_show">Nerodyti</string>
|
||||||
<string name="detail_heart_img_view_description">Širdelė nuo kurėjo</string>
|
<string name="detail_heart_img_view_description">Širdelė nuo kurėjo</string>
|
||||||
<string name="mark_as_watched">Pažymėti kaip peržiūrėtą</string>
|
<string name="mark_as_watched">Pažymėti kaip peržiūrėtą</string>
|
||||||
<string name="show_image_indicators_summary">Rodyti „Picasso“ spalvotas juosteles ant vaizdų, nurodančių jų šaltinį: raudona tinklui, mėlyna diskui ir žalia atmintis</string>
|
|
||||||
<string name="show_image_indicators_title">Rodyti vaizdo indikatorius</string>
|
|
||||||
<string name="remote_search_suggestions">Nuotolinės paieškos pasiūlymai</string>
|
<string name="remote_search_suggestions">Nuotolinės paieškos pasiūlymai</string>
|
||||||
<string name="local_search_suggestions">Vietinės paieškos pasiūlymai</string>
|
<string name="local_search_suggestions">Vietinės paieškos pasiūlymai</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -625,12 +625,10 @@
|
||||||
<string name="start_main_player_fullscreen_summary">Nesākt video atskaņošanu samazinātā režīmā, bet pilnekrāna režīmā, ja automātiskā rotācija ir izslēgta</string>
|
<string name="start_main_player_fullscreen_summary">Nesākt video atskaņošanu samazinātā režīmā, bet pilnekrāna režīmā, ja automātiskā rotācija ir izslēgta</string>
|
||||||
<string name="disable_media_tunneling_title">Izslēgt multivides tuneļošanu</string>
|
<string name="disable_media_tunneling_title">Izslēgt multivides tuneļošanu</string>
|
||||||
<string name="disable_media_tunneling_summary">Izslēdziet multivides tuneļošanu, ja jums video atskaņošanas laikā parādās melns ekrāns vai aizķeršanās</string>
|
<string name="disable_media_tunneling_summary">Izslēdziet multivides tuneļošanu, ja jums video atskaņošanas laikā parādās melns ekrāns vai aizķeršanās</string>
|
||||||
<string name="show_image_indicators_summary">Rādīt krāsainas lentes virs attēliem, norādot to avotu: sarkana - tīkls, zila - disks, zaļa - atmiņa</string>
|
|
||||||
<string name="description_select_enable">Ieslēgt teksta atlasīšanu video aprakstā</string>
|
<string name="description_select_enable">Ieslēgt teksta atlasīšanu video aprakstā</string>
|
||||||
<string name="no_dir_yet">Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi</string>
|
<string name="no_dir_yet">Lejupielādes mape vēl nav iestatīta, izvēlieties noklusējuma lejupielādes mapi</string>
|
||||||
<string name="main_page_content_swipe_remove">Pārvelciet objektus, lai tos noņemtu</string>
|
<string name="main_page_content_swipe_remove">Pārvelciet objektus, lai tos noņemtu</string>
|
||||||
<string name="local_search_suggestions">Lokālie meklēšanas ieteikumi</string>
|
<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="high_quality_larger">Augstas kvalitātes (lielāks)</string>
|
||||||
<string name="check_for_updates">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="manual_update_description">Manuāli pārbaudīt, vai ir atjauninājumi</string>
|
||||||
|
|
|
@ -595,7 +595,6 @@
|
||||||
<string name="notification_action_1_title">രണ്ടാം പ്രവർത്തന ബട്ടൺ</string>
|
<string name="notification_action_1_title">രണ്ടാം പ്രവർത്തന ബട്ടൺ</string>
|
||||||
<string name="notification_action_0_title">ആദ്യ പ്രവർത്തന ബട്ടൺ</string>
|
<string name="notification_action_0_title">ആദ്യ പ്രവർത്തന ബട്ടൺ</string>
|
||||||
<string name="disable_media_tunneling_summary">വീഡിയോ കാണുമ്പോൾ കറുത്ത സ്ക്രീൻ, അവ്യക്തത അനുഭവിക്കുന്നു എങ്കിൽ മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
<string name="disable_media_tunneling_summary">വീഡിയോ കാണുമ്പോൾ കറുത്ത സ്ക്രീൻ, അവ്യക്തത അനുഭവിക്കുന്നു എങ്കിൽ മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
||||||
<string name="show_image_indicators_summary">ഉറവിടം തിരിച്ചറിയാൻ പിക്കാസോ കളർഡ് റിബൺ ചിത്രങ്ങളുടെ മുകളിൽ കാണിക്കുക: നെറ്റ്വർക്കിന് ചുവപ്പ്, ഡിസ്കിനു നീല, മെമ്മറിയിക്ക് പച്ച</string>
|
|
||||||
<string name="seekbar_preview_thumbnail_title">സീക്ബാർ ചെറുചിത്രം പ്രദർശനം</string>
|
<string name="seekbar_preview_thumbnail_title">സീക്ബാർ ചെറുചിത്രം പ്രദർശനം</string>
|
||||||
<string name="detail_heart_img_view_description">സ്നേഹത്തോടെ സൃഷ്ടാവ്</string>
|
<string name="detail_heart_img_view_description">സ്നേഹത്തോടെ സൃഷ്ടാവ്</string>
|
||||||
<string name="description_select_disable">ഡിസ്ക്രിപ്ഷനിലെ ടെക്സ്റ്റ് സെലക്ട് ചെയ്യുവാൻ അനുവദിക്കാതെ ഇരിക്കുക</string>
|
<string name="description_select_disable">ഡിസ്ക്രിപ്ഷനിലെ ടെക്സ്റ്റ് സെലക്ട് ചെയ്യുവാൻ അനുവദിക്കാതെ ഇരിക്കുക</string>
|
||||||
|
@ -630,7 +629,6 @@
|
||||||
<string name="dont_show">കാണിക്കരുത്</string>
|
<string name="dont_show">കാണിക്കരുത്</string>
|
||||||
<string name="low_quality_smaller">കുറഞ്ഞ നിലവാരം (ചെറുത് )</string>
|
<string name="low_quality_smaller">കുറഞ്ഞ നിലവാരം (ചെറുത് )</string>
|
||||||
<string name="high_quality_larger">ഉയർന്ന നിലവാരം (വലിയത് )</string>
|
<string name="high_quality_larger">ഉയർന്ന നിലവാരം (വലിയത് )</string>
|
||||||
<string name="show_image_indicators_title">ഇമേജ് ഇൻഡിക്കേറ്ററുകൾ കാണിക്കുക</string>
|
|
||||||
<string name="disable_media_tunneling_title">മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
<string name="disable_media_tunneling_title">മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
||||||
<string name="no_dir_yet">ഡൌൺലോഡ് ഫോൾഡർ ഇത് വരെയും സെറ്റ് ചെയ്തിട്ടില്ല, സ്ഥിര ഡൌൺലോഡ് ഫോൾഡർ ഇപ്പോൾ തിരഞ്ഞെക്കുക</string>
|
<string name="no_dir_yet">ഡൌൺലോഡ് ഫോൾഡർ ഇത് വരെയും സെറ്റ് ചെയ്തിട്ടില്ല, സ്ഥിര ഡൌൺലോഡ് ഫോൾഡർ ഇപ്പോൾ തിരഞ്ഞെക്കുക</string>
|
||||||
<string name="comments_are_disabled">അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമായിരിക്കുന്നു</string>
|
<string name="comments_are_disabled">അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമായിരിക്കുന്നു</string>
|
||||||
|
|
|
@ -644,9 +644,7 @@
|
||||||
<string name="local_search_suggestions">Lokale søkeforslag</string>
|
<string name="local_search_suggestions">Lokale søkeforslag</string>
|
||||||
<string name="mark_as_watched">Marker som sett</string>
|
<string name="mark_as_watched">Marker som sett</string>
|
||||||
<string name="start_main_player_fullscreen_summary">Ikke start videoer i minispilleren, men bytt til fullskjermsmodus direkte dersom auto-rotering er låst. Du har fremdeles tilgang til minispilleren ved å avslutte fullskjermsvisningen</string>
|
<string name="start_main_player_fullscreen_summary">Ikke start videoer i minispilleren, men bytt til fullskjermsmodus direkte dersom auto-rotering er låst. Du har fremdeles tilgang til minispilleren ved å avslutte fullskjermsvisningen</string>
|
||||||
<string name="show_image_indicators_summary">Vis Picasso-fargede bånd på toppen av bilder for å indikere kilde: Rød for nettverk, blå for disk, og grønn for minne</string>
|
|
||||||
<string name="detail_heart_img_view_description">Hjertemerket av skaperen</string>
|
<string name="detail_heart_img_view_description">Hjertemerket av skaperen</string>
|
||||||
<string name="show_image_indicators_title">Vis bildeindikatorer</string>
|
|
||||||
<string name="main_page_content_swipe_remove">Dra elementer for å fjerne dem</string>
|
<string name="main_page_content_swipe_remove">Dra elementer for å fjerne dem</string>
|
||||||
<string name="start_main_player_fullscreen_title">Start hovedspiller i fullskjerm</string>
|
<string name="start_main_player_fullscreen_title">Start hovedspiller i fullskjerm</string>
|
||||||
<string name="enqueue_next_stream">Still i kø neste</string>
|
<string name="enqueue_next_stream">Still i kø neste</string>
|
||||||
|
|
|
@ -611,7 +611,6 @@
|
||||||
<string name="no_dir_yet">Geen download map ingesteld, kies nu de standaard download map</string>
|
<string name="no_dir_yet">Geen download map ingesteld, kies nu de standaard download map</string>
|
||||||
<string name="dont_show">Niet tonen</string>
|
<string name="dont_show">Niet tonen</string>
|
||||||
<string name="comments_are_disabled">Reacties zijn uitgeschakeld</string>
|
<string name="comments_are_disabled">Reacties zijn uitgeschakeld</string>
|
||||||
<string name="show_image_indicators_title">Toon afbeeldingsindicatoren</string>
|
|
||||||
<string name="settings_category_player_notification_title">Speler melding</string>
|
<string name="settings_category_player_notification_title">Speler melding</string>
|
||||||
<string name="settings_category_player_notification_summary">Configureer actieve stream melding</string>
|
<string name="settings_category_player_notification_summary">Configureer actieve stream melding</string>
|
||||||
<string name="notifications">Meldingen</string>
|
<string name="notifications">Meldingen</string>
|
||||||
|
|
|
@ -632,8 +632,6 @@
|
||||||
<string name="low_quality_smaller">Lage kwaliteit (kleiner)</string>
|
<string name="low_quality_smaller">Lage kwaliteit (kleiner)</string>
|
||||||
<string name="high_quality_larger">Hoge kwaliteit (groter)</string>
|
<string name="high_quality_larger">Hoge kwaliteit (groter)</string>
|
||||||
<string name="seekbar_preview_thumbnail_title">Zoekbalk miniatuurafbeelding voorbeeld</string>
|
<string name="seekbar_preview_thumbnail_title">Zoekbalk miniatuurafbeelding voorbeeld</string>
|
||||||
<string name="show_image_indicators_summary">Toon Picasso-gekleurde linten bovenop afbeeldingen die hun bron aangeven: rood voor netwerk, blauw voor schijf en groen voor geheugen</string>
|
|
||||||
<string name="show_image_indicators_title">Afbeeldingsindicatoren tonen</string>
|
|
||||||
<string name="comments_are_disabled">Reacties zijn uitgeschakeld</string>
|
<string name="comments_are_disabled">Reacties zijn uitgeschakeld</string>
|
||||||
<string name="remote_search_suggestions">Zoeksuggesties op afstand</string>
|
<string name="remote_search_suggestions">Zoeksuggesties op afstand</string>
|
||||||
<string name="local_search_suggestions">Lokale zoeksuggesties</string>
|
<string name="local_search_suggestions">Lokale zoeksuggesties</string>
|
||||||
|
|
|
@ -440,8 +440,6 @@
|
||||||
<string name="show_original_time_ago_summary">ߗߋߢߊߟߌ ߟߎ߬ ߞߟߏߜߍ߫ ߓߐߛߎ߲ߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߕߐ߫ ߟߋ߬ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ ߘߐ߫</string>
|
<string name="show_original_time_ago_summary">ߗߋߢߊߟߌ ߟߎ߬ ߞߟߏߜߍ߫ ߓߐߛߎ߲ߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߕߐ߫ ߟߋ߬ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ ߘߐ߫</string>
|
||||||
<string name="disable_media_tunneling_title">ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬</string>
|
<string name="disable_media_tunneling_title">ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬</string>
|
||||||
<string name="disable_media_tunneling_summary">ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߣߴߌ ߞߊ߬ ߥߊ߲߬ߊߥߊ߲߬ ߝߌ߲ ߦߋ߫ ߥߟߊ߫ ߜߊߘߊ߲ߜߊߘߊ߲ߠߌ߲ ߦߋߡߍ߲ߕߊ ߘߏ߫ ߘߐߛߊߙߌ߫ ߕߎߡߊ</string>
|
<string name="disable_media_tunneling_summary">ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬ ߣߴߌ ߞߊ߬ ߥߊ߲߬ߊߥߊ߲߬ ߝߌ߲ ߦߋ߫ ߥߟߊ߫ ߜߊߘߊ߲ߜߊߘߊ߲ߠߌ߲ ߦߋߡߍ߲ߕߊ ߘߏ߫ ߘߐߛߊߙߌ߫ ߕߎߡߊ</string>
|
||||||
<string name="show_image_indicators_title">ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ߫ ߦߌ߬ߘߊ߬ߟߊ߲ ߠߎ߫ ߝߍ߲߬ߛߍ߲߫</string>
|
|
||||||
<string name="show_image_indicators_summary">ߏ߬ ߦߋ߫ ߔߌߛߊߞߏ߫ ߟߊ߫ ߡߙߎߝߋ߫ ߞߟߐ߬ߡߊ ߟߎ߫ ߟߋ߬ ߝߍ߲߬ߛߍ߲߬ ߠߊ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߎ߫ ߞߎ߲߬ߘߐ߫ ߞߵߊ߬ߟߎ߬ ߓߐߛߎ߲ ߦߌ߬ߘߊ߬: ߥߎߟߋ߲߬ߡߊ߲ ߦߋ߫ ߞߙߏ߬ߝߏ ߕߊ ߘߌ߫߸ ߓߊ߯ߡߊ ߦߋ߫ ߝߘߍ߬ ߜߍߟߍ߲ ߕߊ ߘߌ߫ ߊ߬ߣߌ߫ ߝߙߌߛߌߡߊ ߦߋ߫ ߦߟߌߕߏߟߊ߲ ߕߊ ߘߌ߫</string>
|
|
||||||
<string name="export_ongoing">ߟߊ߬ߓߐ߬ߟߌ ߦߴߌߘߐ߫…</string>
|
<string name="export_ongoing">ߟߊ߬ߓߐ߬ߟߌ ߦߴߌߘߐ߫…</string>
|
||||||
<string name="start">ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬</string>
|
<string name="start">ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬</string>
|
||||||
<string name="comments_are_disabled">ߞߊ߲߬ߞߎߡߊ ߟߎ߬ ߟߊߛߊ߬ߣߍ߲ ߠߋ߬</string>
|
<string name="comments_are_disabled">ߞߊ߲߬ߞߎߡߊ ߟߎ߬ ߟߊߛߊ߬ߣߍ߲ ߠߋ߬</string>
|
||||||
|
|
|
@ -342,7 +342,6 @@
|
||||||
<string name="show_original_time_ago_summary">ସେବାଗୁଡିକରୁ ମୂଳ ଲେଖା ଷ୍ଟ୍ରିମ୍ ଆଇଟମ୍ ଗୁଡିକରେ ଦୃଶ୍ୟମାନ ହେବ</string>
|
<string name="show_original_time_ago_summary">ସେବାଗୁଡିକରୁ ମୂଳ ଲେଖା ଷ୍ଟ୍ରିମ୍ ଆଇଟମ୍ ଗୁଡିକରେ ଦୃଶ୍ୟମାନ ହେବ</string>
|
||||||
<string name="disable_media_tunneling_title">ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ</string>
|
<string name="disable_media_tunneling_title">ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ</string>
|
||||||
<string name="disable_media_tunneling_summary">ଯଦି ଆପଣ ଏକ କଳା ପରଦା ଅନୁଭବ କରନ୍ତି କିମ୍ବା ଭିଡିଓ ପ୍ଲେବେକ୍ ଉପରେ ଝୁଣ୍ଟି ପଡ଼ନ୍ତି ତେବେ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ ।</string>
|
<string name="disable_media_tunneling_summary">ଯଦି ଆପଣ ଏକ କଳା ପରଦା ଅନୁଭବ କରନ୍ତି କିମ୍ବା ଭିଡିଓ ପ୍ଲେବେକ୍ ଉପରେ ଝୁଣ୍ଟି ପଡ଼ନ୍ତି ତେବେ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ ।</string>
|
||||||
<string name="show_image_indicators_summary">ଚିତ୍ରଗୁଡ଼ିକର ଉପରେ ପିକାସୋ ରଙ୍ଗୀନ ଫିତା ଦେଖାନ୍ତୁ: ସେମାନଙ୍କର ଉତ୍ସକୁ ସୂଚାଇଥାଏ: ନେଟୱାର୍କ ପାଇଁ ନାଲି, ଡିସ୍କ ପାଇଁ ନୀଳ ଏବଂ ସ୍ମୃତି ପାଇଁ ସବୁଜ</string>
|
|
||||||
<string name="import_title">ଆମଦାନି କରନ୍ତୁ</string>
|
<string name="import_title">ଆମଦାନି କରନ୍ତୁ</string>
|
||||||
<string name="import_from">ଠାରୁ ଆମଦାନୀ କରନ୍ତୁ</string>
|
<string name="import_from">ଠାରୁ ଆମଦାନୀ କରନ୍ତୁ</string>
|
||||||
<string name="import_ongoing">ଆମଦାନି…</string>
|
<string name="import_ongoing">ଆମଦାନି…</string>
|
||||||
|
@ -657,7 +656,6 @@
|
||||||
<string name="playlist_no_uploader">ଅଟୋ-ଜେନେରେଟ୍ (କୌଣସି ଅପଲୋଡର୍ ମିଳିଲା ନାହିଁ)</string>
|
<string name="playlist_no_uploader">ଅଟୋ-ଜେନେରେଟ୍ (କୌଣସି ଅପଲୋଡର୍ ମିଳିଲା ନାହିଁ)</string>
|
||||||
<string name="resize_fill">ପୁରଣ କରନ୍ତୁ</string>
|
<string name="resize_fill">ପୁରଣ କରନ୍ତୁ</string>
|
||||||
<string name="caption_setting_title">କ୍ୟାପସନ୍</string>
|
<string name="caption_setting_title">କ୍ୟାପସନ୍</string>
|
||||||
<string name="show_image_indicators_title">ପ୍ରତିଛବି ସୂଚକ ଦେଖାନ୍ତୁ</string>
|
|
||||||
<string name="show_crash_the_player_summary">ପ୍ଲେୟାର ବ୍ୟବହାର କରିବା ସମୟରେ ଏକ କ୍ରାସ୍ ବିକଳ୍ପ ଦେଖାଏ</string>
|
<string name="show_crash_the_player_summary">ପ୍ଲେୟାର ବ୍ୟବହାର କରିବା ସମୟରେ ଏକ କ୍ରାସ୍ ବିକଳ୍ପ ଦେଖାଏ</string>
|
||||||
<string name="check_new_streams">ନୂତନ ଷ୍ଟ୍ରିମ୍ ପାଇଁ ଯାଞ୍ଚ ଚଲାନ୍ତୁ</string>
|
<string name="check_new_streams">ନୂତନ ଷ୍ଟ୍ରିମ୍ ପାଇଁ ଯାଞ୍ଚ ଚଲାନ୍ତୁ</string>
|
||||||
<string name="crash_the_app">ଆପ୍ କ୍ରାସ୍ କରନ୍ତୁ</string>
|
<string name="crash_the_app">ଆପ୍ କ୍ରାସ୍ କରନ୍ତୁ</string>
|
||||||
|
|
|
@ -626,8 +626,6 @@
|
||||||
<string name="error_report_notification_title">ਨਿਊਪਾਈਪ ਖਾਮੀ ਤੋਂ ਪ੍ਰਭਾਵਤ ਹੋਈ ਹੈ, ਇੱਥੇ ਨੱਪ ਕੇ ਰਿਪੋਰਟ ਕਰੋ</string>
|
<string name="error_report_notification_title">ਨਿਊਪਾਈਪ ਖਾਮੀ ਤੋਂ ਪ੍ਰਭਾਵਤ ਹੋਈ ਹੈ, ਇੱਥੇ ਨੱਪ ਕੇ ਰਿਪੋਰਟ ਕਰੋ</string>
|
||||||
<string name="error_report_notification_toast">ਇੱਕ ਖਾਮੀ ਪ੍ਰਭਾਵੀ ਹੋਈ ਹੈ, ਨੋਟੀਫੀਕੇਸ਼ਨ ਵੇਖੋ</string>
|
<string name="error_report_notification_toast">ਇੱਕ ਖਾਮੀ ਪ੍ਰਭਾਵੀ ਹੋਈ ਹੈ, ਨੋਟੀਫੀਕੇਸ਼ਨ ਵੇਖੋ</string>
|
||||||
<string name="main_page_content_swipe_remove">ਆਈਟਮਾਂ ਨੂੰ ਇੱਕ ਪਾਸੇ ਖਿੱਚ ਕੇ ਹਟਾਓ</string>
|
<string name="main_page_content_swipe_remove">ਆਈਟਮਾਂ ਨੂੰ ਇੱਕ ਪਾਸੇ ਖਿੱਚ ਕੇ ਹਟਾਓ</string>
|
||||||
<string name="show_image_indicators_title">ਦ੍ਰਿਸ਼ ਸੂਚਕ ਵਿਖਾਓ</string>
|
|
||||||
<string name="show_image_indicators_summary">ਦ੍ਰਿਸ਼ਾਂ ਦੇ ਉੱਪਰ ਉਹਨਾਂ ਦੀ ਸਰੋਤ-ਪਛਾਣ ਲਈ ਪਿਕਾਸੋ ਦੇ ਰੰਗਦਾਰ ਰਿਬਨ ਵਿਖਾਓ : ਨੈੱਟਵਰਕ ਲਈ ਲਾਲ, ਡਿਸਕ ਲਈ ਨੀਲੇ ਤੇ ਮੈਮਰੀ ਲਈ ਹਰੇ</string>
|
|
||||||
<string name="enable_streams_notifications_title">ਨਵੀਂ ਸਟ੍ਰੀਮ ਦੇ ਨੋਟੀਫਿਕੇਸ਼ਨ</string>
|
<string name="enable_streams_notifications_title">ਨਵੀਂ ਸਟ੍ਰੀਮ ਦੇ ਨੋਟੀਫਿਕੇਸ਼ਨ</string>
|
||||||
<string name="detail_pinned_comment_view_description">ਪਿੰਨ ਕੀਤੀ ਟਿੱਪਣੀ</string>
|
<string name="detail_pinned_comment_view_description">ਪਿੰਨ ਕੀਤੀ ਟਿੱਪਣੀ</string>
|
||||||
<string name="checking_updates_toast">ਅੱਪਡੇਟ ਦੀ ਉਪਲੱਬਧਤਾ ਪਰਖੀ ਜਾ ਰਹੀ…</string>
|
<string name="checking_updates_toast">ਅੱਪਡੇਟ ਦੀ ਉਪਲੱਬਧਤਾ ਪਰਖੀ ਜਾ ਰਹੀ…</string>
|
||||||
|
|
|
@ -649,8 +649,6 @@
|
||||||
<string name="dont_show">Nie pokazuj</string>
|
<string name="dont_show">Nie pokazuj</string>
|
||||||
<string name="detail_heart_img_view_description">Serduszko od twórcy</string>
|
<string name="detail_heart_img_view_description">Serduszko od twórcy</string>
|
||||||
<string name="mark_as_watched">Oznacz jako obejrzane</string>
|
<string name="mark_as_watched">Oznacz jako obejrzane</string>
|
||||||
<string name="show_image_indicators_summary">Pokazuj kolorowe wstążki Picasso nad obrazami wskazujące ich źródło: czerwone dla sieci, niebieskie dla dysku i zielone dla pamięci</string>
|
|
||||||
<string name="show_image_indicators_title">Pokazuj wskaźniki obrazu</string>
|
|
||||||
<string name="remote_search_suggestions">Zdalne podpowiedzi wyszukiwania</string>
|
<string name="remote_search_suggestions">Zdalne podpowiedzi wyszukiwania</string>
|
||||||
<string name="local_search_suggestions">Lokalne podpowiedzi wyszukiwania</string>
|
<string name="local_search_suggestions">Lokalne podpowiedzi wyszukiwania</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -644,7 +644,6 @@
|
||||||
<string name="comments_are_disabled">Os comentários estão desabilitados</string>
|
<string name="comments_are_disabled">Os comentários estão desabilitados</string>
|
||||||
<string name="mark_as_watched">Marcar como assistido</string>
|
<string name="mark_as_watched">Marcar como assistido</string>
|
||||||
<string name="detail_heart_img_view_description">Curtido pelo criador</string>
|
<string name="detail_heart_img_view_description">Curtido pelo criador</string>
|
||||||
<string name="show_image_indicators_summary">Exibir fitas coloridas no topo das imagens indicando sua fonte: vermelho para rede, azul para disco e verde para memória</string>
|
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
<item quantity="one">%1$s download excluído</item>
|
<item quantity="one">%1$s download excluído</item>
|
||||||
<item quantity="many">%1$s downloads excluídos</item>
|
<item quantity="many">%1$s downloads excluídos</item>
|
||||||
|
@ -655,7 +654,6 @@
|
||||||
<item quantity="many">%s downloads concluídos</item>
|
<item quantity="many">%s downloads concluídos</item>
|
||||||
<item quantity="other">%s downloads concluídos</item>
|
<item quantity="other">%s downloads concluídos</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="show_image_indicators_title">Mostrar indicadores de imagem</string>
|
|
||||||
<string name="enqueued_next">Adicionado na próxima posição da fila</string>
|
<string name="enqueued_next">Adicionado na próxima posição da fila</string>
|
||||||
<string name="enqueue_next_stream">Enfileira a próxima</string>
|
<string name="enqueue_next_stream">Enfileira a próxima</string>
|
||||||
<string name="main_page_content_swipe_remove">Deslize os itens para remove-los</string>
|
<string name="main_page_content_swipe_remove">Deslize os itens para remove-los</string>
|
||||||
|
|
|
@ -644,8 +644,6 @@
|
||||||
<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 foi definida uma pasta de descarregamento, escolha agora a pasta de descarregamento padrão</string>
|
||||||
<string name="comments_are_disabled">Comentários estão desativados</string>
|
<string name="comments_are_disabled">Comentários estão desativados</string>
|
||||||
<string name="mark_as_watched">Marcar como visto</string>
|
<string name="mark_as_watched">Marcar como visto</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>
|
<string name="remote_search_suggestions">Sugestões de pesquisa remotas</string>
|
||||||
<string name="local_search_suggestions">Sugestões de pesquisa locais</string>
|
<string name="local_search_suggestions">Sugestões de pesquisa locais</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -644,8 +644,6 @@
|
||||||
<string name="low_quality_smaller">Baixa qualidade (menor)</string>
|
<string name="low_quality_smaller">Baixa qualidade (menor)</string>
|
||||||
<string name="high_quality_larger">Alta qualidade (maior)</string>
|
<string name="high_quality_larger">Alta qualidade (maior)</string>
|
||||||
<string name="comments_are_disabled">Os 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>
|
<string name="remote_search_suggestions">Sugestões de pesquisa remotas</string>
|
||||||
<string name="local_search_suggestions">Sugestões de pesquisa locais</string>
|
<string name="local_search_suggestions">Sugestões de pesquisa locais</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -660,8 +660,6 @@
|
||||||
<string name="low_quality_smaller">Calitate scăzută (mai mică)</string>
|
<string name="low_quality_smaller">Calitate scăzută (mai mică)</string>
|
||||||
<string name="high_quality_larger">Calitate înaltă (mai mare)</string>
|
<string name="high_quality_larger">Calitate înaltă (mai mare)</string>
|
||||||
<string name="seekbar_preview_thumbnail_title">Miniatură de previzualizare în bara de derulare</string>
|
<string name="seekbar_preview_thumbnail_title">Miniatură de previzualizare în bara de derulare</string>
|
||||||
<string name="show_image_indicators_summary">Afișați panglici colorate de Picasso deasupra imaginilor, indicând sursa acestora: roșu pentru rețea, albastru pentru disc și verde pentru memorie</string>
|
|
||||||
<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="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="processing_may_take_a_moment">Procesarea.. Poate dura un moment</string>
|
||||||
<string name="check_for_updates">Verifică dacă există actualizări</string>
|
<string name="check_for_updates">Verifică dacă există actualizări</string>
|
||||||
|
|
|
@ -652,8 +652,6 @@
|
||||||
<string name="seekbar_preview_thumbnail_title">Миниатюра над полосой прокрутки</string>
|
<string name="seekbar_preview_thumbnail_title">Миниатюра над полосой прокрутки</string>
|
||||||
<string name="detail_heart_img_view_description">Автору видео понравилось это</string>
|
<string name="detail_heart_img_view_description">Автору видео понравилось это</string>
|
||||||
<string name="mark_as_watched">Пометить проигранным</string>
|
<string name="mark_as_watched">Пометить проигранным</string>
|
||||||
<string name="show_image_indicators_summary">Picasso: указать цветом источник изображений (красный — сеть, синий — диск, зелёный — память)</string>
|
|
||||||
<string name="show_image_indicators_title">Цветные метки на изображениях</string>
|
|
||||||
<string name="remote_search_suggestions">Серверные предложения поиска</string>
|
<string name="remote_search_suggestions">Серверные предложения поиска</string>
|
||||||
<string name="local_search_suggestions">Локальные предложения поиска</string>
|
<string name="local_search_suggestions">Локальные предложения поиска</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -646,8 +646,6 @@
|
||||||
<item quantity="one">%sちぬダウンロードぬかんりょうさびたん</item>
|
<item quantity="one">%sちぬダウンロードぬかんりょうさびたん</item>
|
||||||
<item quantity="other">%sちぬダウンロードぬかんりょうさびたん</item>
|
<item quantity="other">%sちぬダウンロードぬかんりょうさびたん</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="show_image_indicators_summary">ピカソー、がぞうぬういに、がぞうくとぅどぅくるしーきびちするしきさいきしーょうひょうじさびーん: あかーネットワーク、あおーディスク、みどぅれーメモリ</string>
|
|
||||||
<string name="show_image_indicators_title">やしがぞうんかいふぃいょうしきひょうじ</string>
|
|
||||||
<string name="processing_may_take_a_moment">しーょりちゅう… くーてーんじがんがかかいんかむしりやびらん</string>
|
<string name="processing_may_take_a_moment">しーょりちゅう… くーてーんじがんがかかいんかむしりやびらん</string>
|
||||||
<string name="manual_update_description">みーさるバージョンしーゅどうでぃかくにんさびーん</string>
|
<string name="manual_update_description">みーさるバージョンしーゅどうでぃかくにんさびーん</string>
|
||||||
<string name="checking_updates_toast">アップデートかくにんちゅう…</string>
|
<string name="checking_updates_toast">アップデートかくにんちゅう…</string>
|
||||||
|
|
|
@ -270,7 +270,6 @@
|
||||||
<string name="leak_canary_not_available">LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
|
<string name="leak_canary_not_available">LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
|
||||||
<string name="enable_leak_canary_summary">ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
|
<string name="enable_leak_canary_summary">ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
|
||||||
<string name="enable_disposed_exceptions_title">ᱡᱤᱭᱚᱱ ᱪᱤᱠᱤ ᱠᱷᱚᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱷᱮᱜᱟᱨ ᱠᱚ ᱚᱱᱚᱞ ᱢᱮ</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="show_crash_the_player_summary">ᱯᱞᱮᱭᱟᱨ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱮ ᱨᱮ ᱠᱨᱟᱥ ᱚᱯᱥᱚᱱ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ</string>
|
||||||
<string name="import_title">ᱤᱢᱯᱳᱨᱴ</string>
|
<string name="import_title">ᱤᱢᱯᱳᱨᱴ</string>
|
||||||
<string name="import_from">ᱤᱢᱯᱚᱨᱴ</string>
|
<string name="import_from">ᱤᱢᱯᱚᱨᱴ</string>
|
||||||
|
@ -540,7 +539,6 @@
|
||||||
<string name="show_original_time_ago_title">ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ</string>
|
<string name="show_original_time_ago_title">ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ</string>
|
||||||
<string name="show_original_time_ago_summary">ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ</string>
|
<string name="show_original_time_ago_summary">ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ</string>
|
||||||
<string name="disable_media_tunneling_summary">ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾</string>
|
<string name="disable_media_tunneling_summary">ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾</string>
|
||||||
<string name="show_image_indicators_title">ᱪᱤᱛᱟᱹᱨ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
|
|
||||||
<string name="check_new_streams">ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ</string>
|
<string name="check_new_streams">ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ</string>
|
||||||
<string name="create_error_notification">ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ</string>
|
<string name="create_error_notification">ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ</string>
|
||||||
<string name="subscriptions_export_unsuccessful">ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
|
<string name="subscriptions_export_unsuccessful">ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
|
||||||
|
|
|
@ -634,8 +634,6 @@
|
||||||
<string name="comments_are_disabled">Sos cummentos sunt disabilitados</string>
|
<string name="comments_are_disabled">Sos cummentos sunt disabilitados</string>
|
||||||
<string name="detail_heart_img_view_description">Su creadore b\'at postu unu coro</string>
|
<string name="detail_heart_img_view_description">Su creadore b\'at postu unu coro</string>
|
||||||
<string name="mark_as_watched">Marca comente pompiadu</string>
|
<string name="mark_as_watched">Marca comente pompiadu</string>
|
||||||
<string name="show_image_indicators_summary">Ammustra sos listrones colorados de Picasso in subra de sas immàgines chi indicant sa fonte issoro: ruja pro sa retze, biaita pro su discu e birde pro sa memòria</string>
|
|
||||||
<string name="show_image_indicators_title">Ammustra sos indicadores de immàgines</string>
|
|
||||||
<string name="remote_search_suggestions">Impòsitos de chirca remota</string>
|
<string name="remote_search_suggestions">Impòsitos de chirca remota</string>
|
||||||
<string name="local_search_suggestions">Impòsitos de chirca locales</string>
|
<string name="local_search_suggestions">Impòsitos de chirca locales</string>
|
||||||
<plurals name="deleted_downloads_toast">
|
<plurals name="deleted_downloads_toast">
|
||||||
|
|
|
@ -653,8 +653,6 @@
|
||||||
<string name="low_quality_smaller">Nízka kvalita (menšie)</string>
|
<string name="low_quality_smaller">Nízka kvalita (menšie)</string>
|
||||||
<string name="high_quality_larger">Vysoká kvalita (väčš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="seekbar_preview_thumbnail_title">Náhľad miniatúry pri vyhľadávaní</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="main_page_content_swipe_remove">Potiahnutím vymazať</string>
|
||||||
<string name="comments_are_disabled">Komentáre sú zakázané</string>
|
<string name="comments_are_disabled">Komentáre sú zakázané</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_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>
|
||||||
|
|
|
@ -633,8 +633,6 @@
|
||||||
<string name="comments_are_disabled">Fallooyinka waa laxidhay</string>
|
<string name="comments_are_disabled">Fallooyinka waa laxidhay</string>
|
||||||
<string name="detail_heart_img_view_description">Kahelay soosaaraha</string>
|
<string name="detail_heart_img_view_description">Kahelay soosaaraha</string>
|
||||||
<string name="mark_as_watched">Waan daawaday</string>
|
<string name="mark_as_watched">Waan daawaday</string>
|
||||||
<string name="show_image_indicators_summary">Soo bandhig shaambado midabka Picasso leh sawirrada dushooda oo tilmaamaya isha laga keenay: guduud waa khadka, buluug waa kaydka gudaha, cagaar waa kaydka K/G</string>
|
|
||||||
<string name="show_image_indicators_title">Tus tilmaamayaasha sawirka</string>
|
|
||||||
<string name="remote_search_suggestions">Soojeedinada raadinta banaanka</string>
|
<string name="remote_search_suggestions">Soojeedinada raadinta banaanka</string>
|
||||||
<string name="local_search_suggestions">Soojeedinada raadinta gudaha</string>
|
<string name="local_search_suggestions">Soojeedinada raadinta gudaha</string>
|
||||||
<string name="progressive_load_interval_title">Cabirka soodaarida udhexeeya</string>
|
<string name="progressive_load_interval_title">Cabirka soodaarida udhexeeya</string>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user