mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-07-01 07:04: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 {
|
||||
checkstyleVersion = '10.12.1'
|
||||
|
||||
androidxLifecycleVersion = '2.6.2'
|
||||
androidxLifecycleVersion = '2.8.2'
|
||||
androidxRoomVersion = '2.6.1'
|
||||
androidxWorkVersion = '2.8.1'
|
||||
|
||||
|
@ -222,12 +222,14 @@ dependencies {
|
|||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-service:${androidxLifecycleVersion}"
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
|
||||
implementation 'androidx.media:media:1.7.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
||||
implementation "androidx.room:room-ktx:${androidxRoomVersion}"
|
||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
||||
|
@ -267,8 +269,7 @@ dependencies {
|
|||
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
|
||||
|
||||
// Image loading
|
||||
//noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
|
||||
implementation "com.squareup.picasso:picasso:2.8"
|
||||
implementation 'io.coil-kt:coil:2.6.0'
|
||||
|
||||
// Markdown library for Android
|
||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||
|
|
|
@ -129,7 +129,7 @@ class DatabaseMigrationTest {
|
|||
)
|
||||
|
||||
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
|
||||
assertEquals(2, listFromDB.size)
|
||||
|
@ -217,7 +217,7 @@ class DatabaseMigrationTest {
|
|||
)
|
||||
|
||||
val migratedDatabaseV8 = getMigratedDatabase()
|
||||
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
|
||||
val listFromDB = migratedDatabaseV8.searchHistoryDAO().getAll().blockingFirst()
|
||||
|
||||
assertEquals(2, listFromDB.size)
|
||||
assertEquals("abc", listFromDB[0].search)
|
||||
|
@ -283,8 +283,8 @@ class DatabaseMigrationTest {
|
|||
)
|
||||
|
||||
val migratedDatabaseV9 = getMigratedDatabase()
|
||||
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
||||
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
||||
var localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
|
||||
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
|
||||
|
||||
assertEquals(1, localListFromDB.size)
|
||||
assertEquals(localUid2, localListFromDB[0].uid)
|
||||
|
@ -303,8 +303,8 @@ class DatabaseMigrationTest {
|
|||
)
|
||||
)
|
||||
|
||||
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
|
||||
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
|
||||
localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
|
||||
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
|
||||
assertEquals(2, localListFromDB.size)
|
||||
assertEquals(localUid3, localListFromDB[1].uid)
|
||||
assertEquals(-1, localListFromDB[1].displayIndex)
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.database
|
|||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -22,7 +22,6 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo
|
|||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import java.io.IOException
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.streams.toList
|
||||
|
||||
class FeedDAOTest {
|
||||
private lateinit var db: AppDatabase
|
||||
|
@ -94,17 +93,13 @@ class FeedDAOTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun setupUnlinkDelete(time: String) {
|
||||
private fun setupUnlinkDelete(time: String) = runBlocking {
|
||||
clearAndFillTables()
|
||||
Single.fromCallable {
|
||||
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
|
||||
}.blockingSubscribe()
|
||||
Single.fromCallable {
|
||||
streamDAO.deleteOrphans()
|
||||
}.blockingSubscribe()
|
||||
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
|
||||
streamDAO.deleteOrphans()
|
||||
}
|
||||
|
||||
private fun clearAndFillTables() {
|
||||
private suspend fun clearAndFillTables() {
|
||||
db.clearAllTables()
|
||||
streamDAO.insertAll(allStreams)
|
||||
subscriptionDAO.insertAll(
|
||||
|
|
|
@ -41,7 +41,7 @@ class HistoryRecordManagerTest {
|
|||
// 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
|
||||
// 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[0].id).isEqualTo(1)
|
||||
assertThat(entities[0].serviceId).isEqualTo(0)
|
||||
|
@ -59,25 +59,25 @@ class HistoryRecordManagerTest {
|
|||
|
||||
// make sure all 4 were inserted
|
||||
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
|
||||
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).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
||||
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
||||
|
||||
// assert that nothing happens if we delete a search query that does exist in the db
|
||||
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).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
|
||||
.containsExactly(*entries.subList(2, 4).toTypedArray())
|
||||
|
||||
// delete all remaining entries
|
||||
manager.deleteSearchHistory("B").test().await().assertValue(2)
|
||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
|
||||
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -90,11 +90,11 @@ class HistoryRecordManagerTest {
|
|||
|
||||
// make sure all 3 were inserted
|
||||
database.searchHistoryDAO().insertAll(entries)
|
||||
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
|
||||
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries)
|
||||
|
||||
// should remove everything
|
||||
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>) {
|
||||
|
@ -107,7 +107,7 @@ class HistoryRecordManagerTest {
|
|||
// make sure all entries were inserted
|
||||
assertEquals(
|
||||
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))
|
||||
|
||||
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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -8,6 +9,7 @@ import android.util.Log;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationChannelCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
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.settings.NewPipeSettings;
|
||||
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.StateSaver;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.List;
|
||||
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.MissingBackpressureException;
|
||||
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/>.
|
||||
*/
|
||||
|
||||
public class App extends Application {
|
||||
public class App extends Application implements ImageLoaderFactory {
|
||||
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
|
||||
private static final String TAG = App.class.toString();
|
||||
|
||||
|
@ -108,20 +115,33 @@ public class App extends Application {
|
|||
|
||||
// Initialize image loader
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
PicassoHelper.init(this);
|
||||
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this,
|
||||
prefs.getString(getString(R.string.image_quality_key),
|
||||
getString(R.string.image_quality_default))));
|
||||
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
|
||||
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
|
||||
|
||||
configureRxJavaErrorHandler();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
PicassoHelper.terminate();
|
||||
public ImageLoader newImageLoader() {
|
||||
final var isLowRamDevice = ContextCompat.getSystemService(this, ActivityManager.class)
|
||||
.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() {
|
||||
|
|
|
@ -167,8 +167,8 @@ class AboutActivity : AppCompatActivity() {
|
|||
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
|
||||
),
|
||||
SoftwareComponent(
|
||||
"Picasso", "2013", "Square, Inc.",
|
||||
"https://square.github.io/picasso/", StandardLicenses.APACHE2
|
||||
"Coil", "2023", "Coil Contributors",
|
||||
"https://coil-kt.github.io/coil/", StandardLicenses.APACHE2
|
||||
),
|
||||
SoftwareComponent(
|
||||
"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 io.reactivex.rxjava3.core.Flowable
|
||||
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.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||
|
@ -18,9 +19,9 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
|||
import java.time.OffsetDateTime
|
||||
|
||||
@Dao
|
||||
abstract class FeedDAO {
|
||||
interface FeedDAO {
|
||||
@Query("DELETE FROM feed")
|
||||
abstract fun deleteAll(): Int
|
||||
suspend fun deleteAll(): Int
|
||||
|
||||
/**
|
||||
* @param groupId the group id to get feed streams of; use
|
||||
|
@ -86,7 +87,7 @@ abstract class FeedDAO {
|
|||
LIMIT 500
|
||||
"""
|
||||
)
|
||||
abstract fun getStreams(
|
||||
fun getStreams(
|
||||
groupId: Long,
|
||||
includePlayed: Boolean,
|
||||
includePartiallyPlayed: Boolean,
|
||||
|
@ -119,7 +120,7 @@ abstract class FeedDAO {
|
|||
AND s.upload_date <> max_upload_date))
|
||||
"""
|
||||
)
|
||||
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
|
||||
suspend fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
@ -137,22 +138,19 @@ abstract class FeedDAO {
|
|||
)
|
||||
"""
|
||||
)
|
||||
abstract fun unlinkOldLivestreams(subscriptionId: Long)
|
||||
suspend fun unlinkOldLivestreams(subscriptionId: Long)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insert(feedEntity: FeedEntity)
|
||||
suspend fun insertAll(entities: List<FeedEntity>): List<Long>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insertAll(entities: List<FeedEntity>): List<Long>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
internal abstract fun insertLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity): Long
|
||||
suspend fun insertLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity): Long
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.IGNORE)
|
||||
internal abstract fun updateLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity)
|
||||
suspend fun updateLastUpdated(lastUpdatedEntity: FeedLastUpdatedEntity)
|
||||
|
||||
@Transaction
|
||||
open fun setLastUpdatedForSubscription(lastUpdatedEntity: FeedLastUpdatedEntity) {
|
||||
suspend fun setLastUpdatedForSubscription(lastUpdatedEntity: FeedLastUpdatedEntity) {
|
||||
val id = insertLastUpdated(lastUpdatedEntity)
|
||||
|
||||
if (id == -1L) {
|
||||
|
@ -168,13 +166,13 @@ abstract class FeedDAO {
|
|||
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")
|
||||
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
|
||||
fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
|
||||
abstract fun notLoadedCount(): Flowable<Long>
|
||||
fun notLoadedCount(): Flowable<Long>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
@ -189,7 +187,7 @@ abstract class FeedDAO {
|
|||
WHERE lu.last_updated IS NULL
|
||||
"""
|
||||
)
|
||||
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
|
||||
fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
@ -201,7 +199,7 @@ abstract class FeedDAO {
|
|||
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(
|
||||
"""
|
||||
|
@ -216,7 +214,7 @@ abstract class FeedDAO {
|
|||
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(
|
||||
"""
|
||||
|
@ -230,8 +228,8 @@ abstract class FeedDAO {
|
|||
AND s.notification_mode = :notificationMode
|
||||
"""
|
||||
)
|
||||
abstract fun getOutdatedWithNotificationMode(
|
||||
fun getOutdatedWithNotificationMode(
|
||||
outdatedThreshold: OffsetDateTime,
|
||||
@NotificationMode notificationMode: Int
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
): Flow<List<SubscriptionEntity>>
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
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.room.Dao;
|
||||
import androidx.room.Query;
|
||||
|
@ -10,12 +16,6 @@ import java.util.List;
|
|||
|
||||
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
|
||||
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
@ -11,9 +14,6 @@ import java.util.List;
|
|||
|
||||
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
|
||||
public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
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.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
@ -11,12 +17,6 @@ import java.util.List;
|
|||
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_DISPLAY_INDEX;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
|
||||
|
||||
@Dao
|
||||
public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
|
||||
@Override
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.room.Query
|
|||
import androidx.room.Transaction
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
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
|
||||
|
||||
@Dao
|
||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
interface StreamDAO : BasicDAO<StreamEntity> {
|
||||
@Query("SELECT * FROM streams")
|
||||
abstract override fun getAll(): Flowable<List<StreamEntity>>
|
||||
override fun getAll(): Flowable<List<StreamEntity>>
|
||||
|
||||
@Query("DELETE FROM streams")
|
||||
abstract override fun deleteAll(): Int
|
||||
override fun deleteAll(): Int
|
||||
|
||||
@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")
|
||||
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")
|
||||
abstract fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable
|
||||
fun setUploaderUrl(serviceId: Long, url: String, uploaderUrl: String): Completable
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
internal abstract fun silentInsertInternal(stream: StreamEntity): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
internal abstract fun silentInsertAllInternal(streams: List<StreamEntity>): List<Long>
|
||||
fun silentInsertInternal(stream: StreamEntity): Long
|
||||
|
||||
@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(
|
||||
"""
|
||||
|
@ -47,10 +45,10 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
|||
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
|
||||
open fun upsert(newerStream: StreamEntity): Long {
|
||||
fun upsert(newerStream: StreamEntity): Long {
|
||||
val uid = silentInsertInternal(newerStream)
|
||||
|
||||
if (uid != -1L) {
|
||||
|
@ -65,24 +63,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
|||
}
|
||||
|
||||
@Transaction
|
||||
open fun upsertAll(streams: List<StreamEntity>): List<Long> {
|
||||
val insertUidList = silentInsertAllInternal(streams)
|
||||
|
||||
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
|
||||
fun upsertAll(streams: List<StreamEntity>): List<Long> {
|
||||
return streams.map { upsert(it) }
|
||||
}
|
||||
|
||||
private fun compareAndUpdateStream(newerStream: StreamEntity) {
|
||||
|
@ -91,7 +73,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
|||
newerStream.uid = existentMinimalStream.uid
|
||||
|
||||
if (!StreamTypeUtil.isLiveStream(newerStream.streamType)) {
|
||||
|
||||
// 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.
|
||||
val hasBetterPrecision =
|
||||
|
@ -122,12 +103,16 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
|||
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.
|
||||
*/
|
||||
internal data class StreamCompareFeed(
|
||||
data class StreamCompareFeed(
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
|
@ -13,9 +16,6 @@ import java.util.List;
|
|||
|
||||
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
|
||||
public interface StreamStateDAO extends BasicDAO<StreamStateEntity> {
|
||||
@Override
|
||||
|
|
|
@ -6,20 +6,25 @@ import androidx.room.OnConflictStrategy
|
|||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
|
||||
@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")
|
||||
abstract fun rowCount(): Flowable<Long>
|
||||
fun rowCount(): Flowable<Long>
|
||||
|
||||
@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")
|
||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
@ -30,7 +35,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
ORDER BY name COLLATE NOCASE ASC
|
||||
"""
|
||||
)
|
||||
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query(
|
||||
|
@ -45,7 +50,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
ORDER BY name COLLATE NOCASE ASC
|
||||
"""
|
||||
)
|
||||
abstract fun getSubscriptionsOnlyUngrouped(
|
||||
fun getSubscriptionsOnlyUngrouped(
|
||||
currentGroupId: Long
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
|
@ -63,34 +68,34 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
ORDER BY name COLLATE NOCASE ASC
|
||||
"""
|
||||
)
|
||||
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
||||
fun getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId: Long,
|
||||
filter: String
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@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")
|
||||
abstract fun getSubscription(serviceId: Int, url: String): Maybe<SubscriptionEntity>
|
||||
fun getSubscription(serviceId: Int, url: String): Maybe<SubscriptionEntity>
|
||||
|
||||
@Query("SELECT * FROM subscriptions WHERE uid = :subscriptionId")
|
||||
abstract fun getSubscription(subscriptionId: Long): SubscriptionEntity
|
||||
suspend fun getSubscription(subscriptionId: Long): SubscriptionEntity
|
||||
|
||||
@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")
|
||||
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")
|
||||
internal abstract fun getSubscriptionIdInternal(serviceId: Int, url: String): Long?
|
||||
suspend fun getSubscriptionIdInternal(serviceId: Int, url: String): Long?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
internal abstract fun silentInsertAllInternal(entities: List<SubscriptionEntity>): List<Long>
|
||||
suspend fun silentInsertAllInternal(entities: List<SubscriptionEntity>): List<Long>
|
||||
|
||||
@Transaction
|
||||
open fun upsertAll(entities: List<SubscriptionEntity>): List<SubscriptionEntity> {
|
||||
suspend fun upsertAll(entities: List<SubscriptionEntity>): List<SubscriptionEntity> {
|
||||
val insertUidList = silentInsertAllInternal(entities)
|
||||
|
||||
insertUidList.forEachIndexed { index: Int, uidFromInsert: Long ->
|
||||
|
@ -103,7 +108,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
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.external_communication.KoreUtils;
|
||||
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.Iterator;
|
||||
|
@ -127,6 +127,7 @@ import java.util.Optional;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import coil.util.CoilUtils;
|
||||
import icepick.State;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
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 EMPTY_TAB_TAG = "EMPTY TAB";
|
||||
|
||||
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
|
||||
|
||||
// tabs
|
||||
private boolean showComments;
|
||||
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.detailSubChannelThumbnailView.setImageBitmap(null);
|
||||
}
|
||||
|
@ -1562,8 +1564,8 @@ public final class VideoDetailFragment
|
|||
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
||||
|
||||
checkUpdateProgressInfo(info);
|
||||
PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailThumbnailImageView);
|
||||
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView,
|
||||
info.getThumbnails());
|
||||
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
|
||||
binding.detailMetaInfoSeparator, disposables);
|
||||
|
||||
|
@ -1613,8 +1615,8 @@ public final class VideoDetailFragment
|
|||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||
info.getUploaderAvatars());
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||
}
|
||||
|
@ -1645,11 +1647,11 @@ public final class VideoDetailFragment
|
|||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||
info.getSubChannelAvatars());
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailUploaderThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView,
|
||||
info.getUploaderAvatars());
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
@ -2403,8 +2405,7 @@ public final class VideoDetailFragment
|
|||
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
|
||||
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
||||
binding.overlayThumbnail.setImageDrawable(null);
|
||||
PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.overlayThumbnail);
|
||||
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails);
|
||||
}
|
||||
|
||||
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.NavigationHelper;
|
||||
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.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import coil.util.CoilUtils;
|
||||
import icepick.State;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
|
@ -72,7 +73,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
|||
implements StateSaver.WriteRead {
|
||||
|
||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
|
||||
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
|
@ -569,7 +569,9 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
|||
@Override
|
||||
public void 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);
|
||||
}
|
||||
|
||||
|
@ -580,17 +582,15 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
|||
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
|
||||
|
||||
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
|
||||
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.channelBannerImage);
|
||||
CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners());
|
||||
} else {
|
||||
// do not waste space for the banner, if the user disabled images or there is not one
|
||||
binding.channelBannerImage.setImageDrawable(null);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.channelAvatarView);
|
||||
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.subChannelAvatarView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars());
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView,
|
||||
result.getParentChannelAvatars());
|
||||
|
||||
binding.channelTitleView.setText(result.getName());
|
||||
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.Localization;
|
||||
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.PicassoHelper;
|
||||
import org.schabi.newpipe.util.text.TextLinkifier;
|
||||
|
||||
import java.util.Queue;
|
||||
|
@ -82,7 +82,7 @@ public final class CommentRepliesFragment
|
|||
final CommentsInfoItem item = commentsInfoItem;
|
||||
|
||||
// load the author avatar
|
||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars());
|
||||
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
|
||||
? 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.PlayButtonHelper;
|
||||
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 java.util.ArrayList;
|
||||
|
@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import coil.util.CoilUtils;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
|
@ -71,8 +72,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
|
||||
implements PlaylistControlViewHolder {
|
||||
|
||||
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
|
||||
|
||||
private CompositeDisposable disposables;
|
||||
private Subscription bookmarkReactor;
|
||||
private AtomicBoolean isBookmarkButtonReady;
|
||||
|
@ -276,7 +275,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
|||
animate(headerBinding.getRoot(), false, 200);
|
||||
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||
|
||||
PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG);
|
||||
CoilUtils.dispose(headerBinding.uploaderAvatarView);
|
||||
animate(headerBinding.uploaderLayout, false, 200);
|
||||
}
|
||||
|
||||
|
@ -327,8 +326,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
|||
R.drawable.ic_radio)
|
||||
);
|
||||
} else {
|
||||
PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
|
||||
.into(headerBinding.uploaderAvatarView);
|
||||
CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView,
|
||||
result.getUploaderAvatars());
|
||||
}
|
||||
|
||||
streamCount = result.getStreamCount();
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package org.schabi.newpipe.info_list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.xwray.groupie.GroupieViewHolder
|
||||
import com.xwray.groupie.Item
|
||||
import com.xwray.groupie.viewbinding.BindableItem
|
||||
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.databinding.ItemStreamSegmentBinding
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
class StreamSegmentItem(
|
||||
private val item: StreamSegment,
|
||||
private val onClick: StreamSegmentAdapter.StreamSegmentListener
|
||||
) : Item<GroupieViewHolder>() {
|
||||
) : BindableItem<ItemStreamSegmentBinding>() {
|
||||
|
||||
companion object {
|
||||
const val PAYLOAD_SELECT = 1
|
||||
|
@ -21,31 +20,32 @@ class StreamSegmentItem(
|
|||
|
||||
var isSelected = false
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
item.previewUrl?.let {
|
||||
PicassoHelper.loadThumbnail(it)
|
||||
.into(viewHolder.root.findViewById<ImageView>(R.id.previewImage))
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
|
||||
override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) {
|
||||
CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl)
|
||||
viewBinding.textViewTitle.text = item.title
|
||||
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
|
||||
// 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.
|
||||
// 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 {
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).text = item.channelName
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.VISIBLE
|
||||
viewBinding.textViewChannel.text = item.channelName
|
||||
viewBinding.textViewChannel.visibility = View.VISIBLE
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
|
||||
viewBinding.textViewStartSeconds.text =
|
||||
Localization.getDurationString(item.startTimeSeconds.toLong())
|
||||
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||
viewHolder.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
|
||||
viewHolder.root.isSelected = isSelected
|
||||
viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||
viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
|
||||
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)) {
|
||||
viewHolder.root.isSelected = isSelected
|
||||
return
|
||||
|
@ -54,4 +54,6 @@ class StreamSegmentItem(
|
|||
}
|
||||
|
||||
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.info_list.InfoItemBuilder;
|
||||
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.image.CoilHelper;
|
||||
|
||||
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
private final ImageView itemThumbnailView;
|
||||
|
@ -56,7 +56,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
|||
itemAdditionalDetailView.setText(getDetailLine(item));
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
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.NavigationHelper;
|
||||
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.PicassoHelper;
|
||||
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.text.TextEllipsizer;
|
||||
|
||||
|
@ -79,14 +79,12 @@ public class CommentInfoItemHolder extends InfoItemHolder {
|
|||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem,
|
||||
final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof CommentsInfoItem)) {
|
||||
if (!(infoItem instanceof CommentsInfoItem item)) {
|
||||
return;
|
||||
}
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
|
||||
// load the author avatar
|
||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getUploaderAvatars());
|
||||
if (ImageStrategy.shouldLoadImages()) {
|
||||
itemThumbnailView.setVisibility(View.VISIBLE);
|
||||
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.info_list.InfoItemBuilder;
|
||||
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.image.CoilHelper;
|
||||
|
||||
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final ImageView itemThumbnailView;
|
||||
|
@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
|||
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
|
||||
itemUploaderView.setText(item.getUploaderName());
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
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.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
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
|
||||
PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
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
|
||||
) = feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
|
||||
|
||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||
suspend fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
||||
|
||||
fun doesStreamExist(stream: StreamInfoItem): Boolean {
|
||||
suspend fun doesStreamExist(stream: StreamInfoItem): Boolean {
|
||||
return streamTable.exists(stream.serviceId, stream.url)
|
||||
}
|
||||
|
||||
fun upsertAll(
|
||||
suspend fun upsertAll(
|
||||
subscriptionId: Long,
|
||||
items: List<StreamInfoItem>,
|
||||
oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE
|
||||
) {
|
||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
||||
loop@ for (streamItem in items) {
|
||||
val uploadDate = streamItem.uploadDate
|
||||
|
||||
itemsToInsert += when {
|
||||
uploadDate == null && streamItem.streamType == StreamType.LIVE_STREAM -> streamItem
|
||||
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate -> streamItem
|
||||
else -> continue@loop
|
||||
}
|
||||
val itemsToInsert = items.filter {
|
||||
val uploadDate = it.uploadDate
|
||||
uploadDate == null && it.streamType == StreamType.LIVE_STREAM ||
|
||||
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate
|
||||
}
|
||||
|
||||
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)
|
||||
streamTable.deleteOrphans()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
suspend fun clear() {
|
||||
feedTable.deleteAll()
|
||||
val deletedOrphans = streamTable.deleteOrphans()
|
||||
if (DEBUG) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import androidx.core.content.edit
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -49,10 +50,9 @@ import com.xwray.groupie.Item
|
|||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.OnItemLongClickListener
|
||||
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.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
|
@ -253,7 +253,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
viewModel.getShowFutureItemsFromPreferences()
|
||||
)
|
||||
|
||||
AlertDialog.Builder(context!!)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.feed_hide_streams_title)
|
||||
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
||||
checkedDialogItems[which] = isChecked
|
||||
|
@ -453,24 +453,20 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||
if (t is FeedLoadService.RequestException &&
|
||||
t.cause is ContentNotAvailableException
|
||||
) {
|
||||
disposables.add(
|
||||
Single.fromCallable {
|
||||
NewPipeDatabase.getInstance(requireContext()).subscriptionDAO()
|
||||
.getSubscription(t.subscriptionId)
|
||||
lifecycleScope.launch(
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
Log.e(TAG, "Unable to process", throwable)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ subscriptionEntity ->
|
||||
handleFeedNotAvailable(
|
||||
subscriptionEntity,
|
||||
t.cause,
|
||||
errors.subList(i + 1, errors.size)
|
||||
)
|
||||
},
|
||||
{ throwable -> Log.e(TAG, "Unable to process", throwable) }
|
||||
)
|
||||
)
|
||||
) {
|
||||
val subscriptionEntity = NewPipeDatabase.getInstance(requireContext())
|
||||
.subscriptionDAO()
|
||||
.getSubscription(t.subscriptionId)
|
||||
handleFeedNotAvailable(
|
||||
subscriptionEntity,
|
||||
t.cause,
|
||||
errors.subList(i + 1, errors.size)
|
||||
)
|
||||
}
|
||||
// this will be called on the remaining errors by handleFeedNotAvailable()
|
||||
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.util.Localization
|
||||
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.function.Consumer
|
||||
|
||||
|
@ -101,7 +101,7 @@ data class StreamItem(
|
|||
viewBinding.itemProgressView.visibility = View.GONE
|
||||
}
|
||||
|
||||
PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView)
|
||||
CoilHelper.loadThumbnail(viewBinding.itemThumbnailView, stream.thumbnailUrl)
|
||||
|
||||
if (itemVersion != ItemVersion.MINI) {
|
||||
viewBinding.itemAdditionalDetails.text =
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.app.PendingIntent
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
|
@ -16,20 +15,17 @@ import androidx.core.app.PendingIntentCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.Target
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
|
||||
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.
|
||||
*/
|
||||
class NotificationHelper(val context: 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
|
||||
|
@ -38,7 +34,7 @@ class NotificationHelper(val context: Context) {
|
|||
* Opening the summary notification will open the corresponding channel page. Opening the
|
||||
* individual notifications will open the corresponding video.
|
||||
*/
|
||||
fun displayNewStreamsNotifications(data: FeedUpdateInfo) {
|
||||
suspend fun displayNewStreamsNotifications(data: FeedUpdateInfo) {
|
||||
val newStreams = data.newStreams
|
||||
val summary = context.resources.getQuantityString(
|
||||
R.plurals.new_streams, newStreams.size, newStreams.size
|
||||
|
@ -68,51 +64,23 @@ class NotificationHelper(val context: Context) {
|
|||
summaryBuilder.setStyle(style)
|
||||
|
||||
// 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(
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
data.pseudoId,
|
||||
NavigationHelper
|
||||
.getChannelIntent(context, data.serviceId, data.url)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
0,
|
||||
false
|
||||
)
|
||||
PendingIntentCompat.getActivity(context, data.pseudoId, intent, 0, false)
|
||||
)
|
||||
|
||||
// a Target is like a listener for image loading events
|
||||
val target = object : Target {
|
||||
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)
|
||||
val avatarIcon =
|
||||
CoilHelper.loadBitmap(context, data.avatarUrl, R.drawable.ic_newpipe_triangle_white)
|
||||
|
||||
// Show individual stream notifications, set channel icon only if there is actually
|
||||
// one
|
||||
showStreamNotifications(newStreams, data.serviceId, bitmap)
|
||||
// Show summary notification
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
summaryBuilder.setLargeIcon(avatarIcon)
|
||||
|
||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||
}
|
||||
|
||||
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
||||
// Show individual stream notifications
|
||||
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)
|
||||
// Show individual stream notifications, set channel icon only if there is actually
|
||||
// one
|
||||
showStreamNotifications(newStreams, data.serviceId, avatarIcon)
|
||||
// Show summary notification
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
}
|
||||
|
||||
private fun showStreamNotifications(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
|
@ -11,9 +12,6 @@ import androidx.work.OneTimeWorkRequestBuilder
|
|||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
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.R
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
|
@ -30,46 +28,38 @@ import java.util.concurrent.TimeUnit
|
|||
class NotificationWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters,
|
||||
) : RxWorker(appContext, workerParams) {
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
private val notificationHelper by lazy {
|
||||
NotificationHelper(appContext)
|
||||
}
|
||||
private val feedLoadManager = FeedLoadManager(appContext)
|
||||
|
||||
override fun createWork(): Single<Result> = if (areNotificationsEnabled(applicationContext)) {
|
||||
feedLoadManager.startLoading(
|
||||
ignoreOutdatedThreshold = true,
|
||||
groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED
|
||||
)
|
||||
.doOnSubscribe { showLoadingFeedForegroundNotification() }
|
||||
.map { feed ->
|
||||
// filter out feedUpdateInfo items (i.e. channels) with nothing new
|
||||
feed.mapNotNull {
|
||||
it.value?.takeIf { feedUpdateInfo ->
|
||||
feedUpdateInfo.newStreams.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread
|
||||
.map { feedUpdateInfoList ->
|
||||
override suspend fun doWork(): Result {
|
||||
return if (areNotificationsEnabled(applicationContext)) {
|
||||
try {
|
||||
showLoadingFeedForegroundNotification()
|
||||
val feedUpdateInfoList = feedLoadManager
|
||||
.startLoading(FeedLoadManager.GROUP_NOTIFICATION_ENABLED, true)
|
||||
// filter out feedUpdateInfo items (i.e. channels) with nothing new
|
||||
.mapNotNull { it.value?.takeIf { it.newStreams.isNotEmpty() } }
|
||||
|
||||
// display notifications for each feedUpdateInfo (i.e. channel)
|
||||
feedUpdateInfoList.forEach { feedUpdateInfo ->
|
||||
notificationHelper.displayNewStreamsNotifications(feedUpdateInfo)
|
||||
feedUpdateInfoList.forEach {
|
||||
notificationHelper.displayNewStreamsNotifications(it)
|
||||
}
|
||||
return@map Result.success()
|
||||
}
|
||||
.doOnError { throwable ->
|
||||
Log.e(TAG, "Error while displaying streams notifications", throwable)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while displaying streams notifications", e)
|
||||
ErrorUtil.createNotification(
|
||||
applicationContext,
|
||||
ErrorInfo(throwable, UserAction.NEW_STREAMS_NOTIFICATIONS, "main worker")
|
||||
ErrorInfo(e, UserAction.NEW_STREAMS_NOTIFICATIONS, "main worker")
|
||||
)
|
||||
Result.failure()
|
||||
}
|
||||
.onErrorReturnItem(Result.failure())
|
||||
} else {
|
||||
// the user can disable streams notifications in the device's app settings
|
||||
Single.just(Result.success())
|
||||
} else {
|
||||
Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoadingFeedForegroundNotification() {
|
||||
|
|
|
@ -3,14 +3,20 @@ package org.schabi.newpipe.local.feed.service
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import androidx.room.withTransaction
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
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.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.database.feed.model.FeedGroupEntity
|
||||
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
|
||||
* 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,
|
||||
ignoreOutdatedThreshold: Boolean = false,
|
||||
): Single<List<Notification<FeedUpdateInfo>>> {
|
||||
): List<Notification<FeedUpdateInfo>> {
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val useFeedExtractor = defaultSharedPreferences.getBoolean(
|
||||
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)
|
||||
}
|
||||
|
||||
return outdatedSubscriptions
|
||||
.take(1)
|
||||
.doOnNext {
|
||||
currentProgress.set(0)
|
||||
maxProgress.set(it.size)
|
||||
val notifications = withContext(Dispatchers.IO) {
|
||||
outdatedSubscriptions
|
||||
.take(1)
|
||||
.onEach {
|
||||
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 {
|
||||
notificationUpdater.onNext("")
|
||||
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()) }
|
||||
}
|
||||
|
||||
postProcessFeed()
|
||||
return notifications
|
||||
}
|
||||
|
||||
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 database which are not linked / used by any table.
|
||||
*/
|
||||
private fun postProcessFeed() = Completable.fromRunnable {
|
||||
FeedEventManager.postEvent(FeedEventManager.Event.ProgressEvent(R.string.feed_processing_message))
|
||||
feedDatabaseManager.removeOrphansOrOlderStreams()
|
||||
private suspend fun postProcessFeed() {
|
||||
withContext(Dispatchers.IO) {
|
||||
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)
|
||||
maxProgress.set(-1)
|
||||
|
||||
notificationUpdater.onNext(context.getString(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>>> {
|
||||
|
||||
override fun accept(list: List<Notification<FeedUpdateInfo>>) {
|
||||
feedDatabaseManager.database().runInTransaction {
|
||||
for (notification in list) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
private suspend 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() > FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,22 +19,24 @@
|
|||
|
||||
package org.schabi.newpipe.local.feed.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.functions.Function
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import org.schabi.newpipe.App
|
||||
import org.schabi.newpipe.MainActivity.DEBUG
|
||||
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 java.util.concurrent.TimeUnit
|
||||
|
||||
class FeedLoadService : Service() {
|
||||
class FeedLoadService : LifecycleService() {
|
||||
companion object {
|
||||
private val TAG = FeedLoadService::class.java.simpleName
|
||||
const val NOTIFICATION_ID = 7293450
|
||||
|
@ -72,35 +74,34 @@ class FeedLoadService : Service() {
|
|||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"onStartCommand() called with: intent = [" + intent + "]," +
|
||||
" flags = [" + flags + "], startId = [" + startId + "]"
|
||||
"onStartCommand() called with: intent = [$intent], flags = [$flags], " +
|
||||
"startId = [$startId]"
|
||||
)
|
||||
}
|
||||
|
||||
if (intent == null || loadingDisposable != null) {
|
||||
if (intent == null) {
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
setupNotification()
|
||||
setupBroadcastReceiver()
|
||||
|
||||
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
||||
loadingDisposable = feedLoadManager.startLoading(groupId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSubscribe {
|
||||
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()
|
||||
lifecycleScope.launch(
|
||||
CoroutineExceptionHandler { _, throwable ->
|
||||
Log.e(TAG, "Error while storing result", throwable)
|
||||
handleError(throwable)
|
||||
}
|
||||
) {
|
||||
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
||||
startForeground(NOTIFICATION_ID, notificationBuilder.build())
|
||||
feedLoadManager.startLoading(groupId)
|
||||
stopService()
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
|
@ -116,10 +117,6 @@ class FeedLoadService : Service() {
|
|||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Loading & Handling
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -312,7 +312,6 @@ public class HistoryRecordManager {
|
|||
///////////////////////////////////////////////////////
|
||||
|
||||
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.local.LocalItemBuilder;
|
||||
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.image.CoilHelper;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
@ -30,17 +30,16 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
|||
public void updateFromItem(final LocalItem localItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final DateTimeFormatter dateTimeFormatter) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry item)) {
|
||||
return;
|
||||
}
|
||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
||||
|
||||
itemTitleView.setText(item.name);
|
||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||
itemStreamCountView.getContext(), item.streamCount));
|
||||
itemUploaderView.setVisibility(View.INVISIBLE);
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.thumbnailUrl).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.thumbnailUrl);
|
||||
|
||||
if (item instanceof PlaylistDuplicatesEntry
|
||||
&& ((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.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
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
|
||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
||||
.into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||
item.getStreamEntity().getThumbnailUrl());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
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.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
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
|
||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
||||
.into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||
item.getStreamEntity().getThumbnailUrl());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
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.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
@ -29,10 +29,9 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
|||
public void updateFromItem(final LocalItem localItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final DateTimeFormatter dateTimeFormatter) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity)) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity item)) {
|
||||
return;
|
||||
}
|
||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
||||
|
||||
itemTitleView.setText(item.getName());
|
||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||
|
@ -45,7 +44,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
|||
itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId()));
|
||||
}
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl());
|
||||
|
||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package org.schabi.newpipe.local.subscription
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Pair
|
||||
import androidx.room.withTransaction
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
|
@ -26,7 +28,7 @@ class SubscriptionManager(context: Context) {
|
|||
private val feedDatabaseManager = FeedDatabaseManager(context)
|
||||
|
||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
fun subscriptions() = subscriptionTable.all
|
||||
fun subscriptions() = subscriptionTable.getAll()
|
||||
|
||||
fun getSubscriptions(
|
||||
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
|
@ -44,16 +46,18 @@ class SubscriptionManager(context: Context) {
|
|||
}
|
||||
}
|
||||
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(
|
||||
infoList.map { SubscriptionEntity.from(it.first) }
|
||||
)
|
||||
|
||||
database.runInTransaction {
|
||||
database.withTransaction {
|
||||
infoList.forEachIndexed { index, info ->
|
||||
info.second.forEach {
|
||||
feedDatabaseManager.upsertAll(
|
||||
|
@ -64,7 +68,7 @@ class SubscriptionManager(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
return listEntities
|
||||
listEntities
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
subscriptionEntity.name = info.name
|
||||
|
@ -108,7 +112,7 @@ class SubscriptionManager(context: Context) {
|
|||
info.description?.let { subscriptionEntity.description = it }
|
||||
info.subscriberCount?.let { subscriptionEntity.subscriberCount = it }
|
||||
|
||||
subscriptionTable.update(subscriptionEntity)
|
||||
subscriptionTable.updateSubscription(subscriptionEntity)
|
||||
}
|
||||
|
||||
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.util.Localization
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
class ChannelItem(
|
||||
private val infoItem: ChannelInfoItem,
|
||||
|
@ -39,7 +39,7 @@ class ChannelItem(
|
|||
itemChannelDescriptionView.text = infoItem.description
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView)
|
||||
CoilHelper.loadAvatar(itemThumbnailView, infoItem.thumbnails)
|
||||
|
||||
gesturesListener?.run {
|
||||
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.ktx.AnimationType
|
||||
import org.schabi.newpipe.ktx.animate
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
|
@ -21,7 +21,7 @@ data class PickerSubscriptionItem(
|
|||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
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.selectedHighlight.isVisible = isSelected
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import android.view.LayoutInflater;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.DrawableKt;
|
||||
import androidx.core.math.MathUtils;
|
||||
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.upstream.DefaultBandwidthMeter;
|
||||
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.R;
|
||||
|
@ -86,8 +85,8 @@ import org.schabi.newpipe.databinding.PlayerBinding;
|
|||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
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.StreamType;
|
||||
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.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import coil.target.Target;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
|
@ -174,7 +174,6 @@ public final class Player implements PlaybackListener, Listener {
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final int RENDERER_UNAVAILABLE = -1;
|
||||
private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
|
@ -246,12 +245,6 @@ public final class Player implements PlaybackListener, Listener {
|
|||
@NonNull
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -295,8 +288,6 @@ public final class Player implements PlaybackListener, Listener {
|
|||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||
|
||||
currentThumbnailTarget = getCurrentThumbnailTarget();
|
||||
|
||||
// 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
|
||||
// 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();
|
||||
progressUpdateDisposable.set(null);
|
||||
cancelLoadingCurrentThumbnail();
|
||||
|
||||
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
|
||||
|
||||
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) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
|
||||
+ thumbnails.size() + "]");
|
||||
}
|
||||
|
||||
// first cancel any previous loading
|
||||
cancelLoadingCurrentThumbnail();
|
||||
|
||||
// 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);
|
||||
if (thumbnails.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// scale down the notification thumbnail for performance
|
||||
PicassoHelper.loadScaledDownThumbnail(context, thumbnails)
|
||||
.tag(PICASSO_PLAYER_THUMBNAIL_TAG)
|
||||
.into(currentThumbnailTarget);
|
||||
}
|
||||
final var target = new Target() {
|
||||
@Override
|
||||
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() {
|
||||
// cancel the Picasso job associated with the player thumbnail, if any
|
||||
PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG);
|
||||
@Override
|
||||
public void onStart(@Nullable final Drawable placeholder) {
|
||||
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) {
|
||||
// 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
|
||||
// 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) {
|
||||
currentThumbnail = bitmap;
|
||||
UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap));
|
||||
|
|
|
@ -6,8 +6,8 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
public class PlayQueueItemBuilder {
|
||||
private static final String TAG = PlayQueueItemBuilder.class.toString();
|
||||
|
@ -33,7 +33,7 @@ public class PlayQueueItemBuilder {
|
|||
holder.itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(holder.itemThumbnailView, item.getThumbnails());
|
||||
|
||||
holder.itemRoot.setOnClickListener(view -> {
|
||||
if (onItemClickListener != null) {
|
||||
|
|
|
@ -13,8 +13,9 @@ import androidx.collection.SparseArrayCompat;
|
|||
|
||||
import com.google.common.base.Stopwatch;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
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.List;
|
||||
|
@ -177,8 +178,8 @@ public class SeekbarPreviewThumbnailHolder {
|
|||
Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'");
|
||||
|
||||
// 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
|
||||
final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
|
||||
// Ensure that you are not running on the main thread, otherwise this will hang
|
||||
final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getApp(), url);
|
||||
|
||||
if (sw != null) {
|
||||
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.Localization;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.image.PreferredImageQuality;
|
||||
|
||||
import java.io.IOException;
|
||||
import coil.Coil;
|
||||
|
||||
public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
private String youtubeRestrictedModeEnabledKey;
|
||||
|
@ -42,14 +41,17 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||
(preference, newValue) -> {
|
||||
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
|
||||
.fromPreferenceKey(requireContext(), (String) newValue));
|
||||
try {
|
||||
PicassoHelper.clearCache(preference.getContext());
|
||||
Toast.makeText(preference.getContext(),
|
||||
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "Unable to clear Picasso cache", e);
|
||||
final var loader = Coil.imageLoader(preference.getContext());
|
||||
if (loader.getMemoryCache() != null) {
|
||||
loader.getMemoryCache().clear();
|
||||
}
|
||||
if (loader.getDiskCache() != null) {
|
||||
loader.getDiskCache().clear();
|
||||
}
|
||||
Toast.makeText(preference.getContext(),
|
||||
R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.schabi.newpipe.error.ErrorInfo;
|
|||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -25,8 +24,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
|||
findPreference(getString(R.string.allow_heap_dumping_key));
|
||||
final Preference showMemoryLeaksPreference =
|
||||
findPreference(getString(R.string.show_memory_leaks_key));
|
||||
final Preference showImageIndicatorsPreference =
|
||||
findPreference(getString(R.string.show_image_indicators_key));
|
||||
final Preference checkNewStreamsPreference =
|
||||
findPreference(getString(R.string.check_new_streams_key));
|
||||
final Preference crashTheAppPreference =
|
||||
|
@ -38,7 +35,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
|||
|
||||
assert allowHeapDumpingPreference != null;
|
||||
assert showMemoryLeaksPreference != null;
|
||||
assert showImageIndicatorsPreference != null;
|
||||
assert checkNewStreamsPreference != null;
|
||||
assert crashTheAppPreference != null;
|
||||
assert showErrorSnackbarPreference != null;
|
||||
|
@ -61,11 +57,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
|||
showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available);
|
||||
}
|
||||
|
||||
showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
|
||||
return true;
|
||||
});
|
||||
|
||||
checkNewStreamsPreference.setOnPreferenceClickListener(preference -> {
|
||||
NotificationWorker.runNow(preference.getContext());
|
||||
return true;
|
||||
|
|
|
@ -19,8 +19,8 @@ import org.schabi.newpipe.R;
|
|||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
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.image.CoilHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
@ -190,7 +190,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||
final SubscriptionEntity entry = subscriptions.get(position);
|
||||
holder.titleView.setText(entry.getName());
|
||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||
PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(holder.thumbnailView, entry.getAvatarUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.schabi.newpipe.error.ErrorUtil;
|
|||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
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.Vector;
|
||||
|
@ -154,20 +154,15 @@ public class SelectPlaylistFragment extends DialogFragment {
|
|||
final int position) {
|
||||
final PlaylistLocalItem selectedItem = playlists.get(position);
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry entry) {
|
||||
holder.titleView.setText(entry.name);
|
||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||
PicassoHelper.loadPlaylistThumbnail(entry.thumbnailUrl).into(holder.thumbnailView);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView, entry.thumbnailUrl);
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity entry) {
|
||||
holder.titleView.setText(entry.getName());
|
||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||
PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl())
|
||||
.into(holder.thumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView,
|
||||
entry.getThumbnailUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import androidx.core.content.FileProvider;
|
|||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
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.PicassoHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
@ -278,7 +278,7 @@ public final class ShareUtils {
|
|||
* @param content the content to share
|
||||
* @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
|
||||
* 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,
|
||||
@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.
|
||||
*
|
||||
* <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>
|
||||
* Generate a {@link ClipData} with the image of the content shared.
|
||||
*
|
||||
* <p>
|
||||
* 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>
|
||||
* This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
|
||||
* thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
|
||||
* the Picasso library inside {@link PicassoHelper}.
|
||||
* This method will call {@link CoilHelper#loadBitmap(Context, String)} to get the
|
||||
* thumbnail of the content.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
@ -378,7 +369,7 @@ public final class ShareUtils {
|
|||
@NonNull final Context context,
|
||||
@NonNull final String thumbnailUrl) {
|
||||
try {
|
||||
final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
|
||||
final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(context, thumbnailUrl);
|
||||
if (bitmap == 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>
|
||||
</plurals>
|
||||
<string name="loading_metadata_title">جلب البيانات الوصفية…</string>
|
||||
<string name="show_image_indicators_title">إظهار مؤشرات الصور</string>
|
||||
<string name="app_update_available_notification_text">انقر للتنزيل %s</string>
|
||||
<string name="feed_use_dedicated_fetch_method_disable_button">تعطيل الوضع السريع</string>
|
||||
<string name="enumeration_comma">,</string>
|
||||
|
@ -830,7 +829,6 @@
|
|||
<string name="you_successfully_subscribed">لقد اشتركت الآن في هذه القناة</string>
|
||||
<string name="downloads_storage_use_saf_summary_api_29">بدءًا من Android 10، يتم دعم \"Storage Access Framework\" فقط</string>
|
||||
<string name="generate_unique_name">إنشاء اسم فريد</string>
|
||||
<string name="show_image_indicators_summary">أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة</string>
|
||||
<string name="error_ssl_exception">فشل الاتصال الآمن</string>
|
||||
<string name="youtube_music_premium_content">يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe.</string>
|
||||
<string name="previous_stream">البث السابق</string>
|
||||
|
|
|
@ -674,8 +674,6 @@
|
|||
<string name="seekbar_preview_thumbnail_title">معاينة مصغرة على شريط التمرير</string>
|
||||
<string name="mark_as_watched">وضع علامة على تمت مشاهدته</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="local_search_suggestions">اقتراحات البحث المحلية</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="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="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="close">Bağla</string>
|
||||
<string name="error_progress_lost">Fayl silindiyi üçün irəliləyiş itirildi</string>
|
||||
|
|
|
@ -630,8 +630,6 @@
|
|||
<string name="faq">Перайсці на вэбсайт</string>
|
||||
<string name="main_page_content_swipe_remove">Правядзіце пальцам па элементах, каб выдаліць іх</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="downloads_storage_ask_summary_no_saf_notice">Вам будзе прапанавана, дзе захоўваць кожную спампоўку</string>
|
||||
<string name="feed_notification_loading">Загрузка стужкі…</string>
|
||||
|
|
|
@ -539,7 +539,6 @@
|
|||
<string name="restricted_video">Това видео е с възрастова граница.
|
||||
\n
|
||||
\nВключете „%1$s“ в настройките ако искате да го пуснете.</string>
|
||||
<string name="show_image_indicators_summary">Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта)</string>
|
||||
<string name="auto_device_theme_title">Автоматична (тази на устройството)</string>
|
||||
<string name="notification_scale_to_square_image_summary">Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания)</string>
|
||||
<string name="select_a_playlist">Избете плейлист</string>
|
||||
|
|
|
@ -589,7 +589,6 @@
|
|||
<string name="streams_notification_channel_name">নতুন ধারা</string>
|
||||
<string name="streams_notifications_interval_title">কম্পাঙ্ক দেখো</string>
|
||||
<string name="seekbar_preview_thumbnail_title">পূর্বদর্শন রেখার মাধ্যমে প্রাকদর্শন</string>
|
||||
<string name="show_image_indicators_title">ছবিরূপ সূচক দেখাও</string>
|
||||
<string name="dont_show">দেখিও না</string>
|
||||
<string name="any_network">যেকোনো নেটওয়ার্ক</string>
|
||||
<string name="enqueued_next">পরেরটা ক্রমে রাখা হয়েছে</string>
|
||||
|
|
|
@ -625,7 +625,6 @@
|
|||
<string name="dont_show">No mostris</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="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="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>
|
||||
|
@ -669,7 +668,6 @@
|
|||
<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_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="streams_notifications_interval_title">Comprovant freqüència</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="low_quality_smaller">کواڵێتی نزم (بچووکتر)</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="local_search_suggestions">پێشنیازکراوەکانی گەڕانی نێوخۆیی</string>
|
||||
<string name="mark_as_watched">دیارکردن وەک بینراو</string>
|
||||
|
|
|
@ -654,8 +654,6 @@
|
|||
<item quantity="few">%s stahování dokončena</item>
|
||||
<item quantity="other">%s stahováních dokončeno</item>
|
||||
</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="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>
|
||||
|
|
|
@ -516,7 +516,6 @@
|
|||
<string name="processing_may_take_a_moment">Behandler… Det kan tage et øjeblik</string>
|
||||
<string name="show_memory_leaks">Vis hukommelseslækager</string>
|
||||
<string name="disable_media_tunneling_title">Deaktivér medietunneling</string>
|
||||
<string name="show_image_indicators_title">Vis billedindikatorer</string>
|
||||
<string name="streams_notifications_network_title">Netværkskrav</string>
|
||||
<string name="any_network">Alle netværk</string>
|
||||
<string name="streams_notifications_interval_title">Kontrolfrekvens</string>
|
||||
|
@ -695,7 +694,6 @@
|
|||
<string name="faq_title">Ofte stillede spørgsmål</string>
|
||||
<string name="faq_description">Hvis du har problemer med at bruge appen, bør du tjekke disse svar på almindelige spørgsmål!</string>
|
||||
<string name="faq">Se på webside</string>
|
||||
<string name="show_image_indicators_summary">Vis Picasso-farvede bånd oven på billeder, der angiver deres kilde: rød for netværk, blå for disk og grøn for hukommelse</string>
|
||||
<string name="app_update_unavailable_toast">Du kører den nyeste version af NewPipe</string>
|
||||
<string name="new_seek_duration_toast">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>
|
||||
|
|
|
@ -637,8 +637,6 @@
|
|||
<string name="off">Aus</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="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="local_search_suggestions">Lokale Suchvorschläge</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -634,8 +634,6 @@
|
|||
<string name="seekbar_preview_thumbnail_title">Προεπισκόπηση στην μπάρα αναζήτησης</string>
|
||||
<string name="mark_as_watched">Σήμανση ως αναπαραχθέν</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="local_search_suggestions">Προτάσεις τοπικής αναζήτησης</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -647,8 +647,6 @@
|
|||
<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="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="local_search_suggestions">Sugerencias de búsqueda local</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -634,8 +634,6 @@
|
|||
\n
|
||||
\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="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="local_search_suggestions">Kohaliku otsingu soovitused</string>
|
||||
<string name="main_page_content_swipe_remove">Üksuse eemaldamiseks viipa</string>
|
||||
|
|
|
@ -641,8 +641,6 @@
|
|||
<item quantity="one">Deskarga amaituta</item>
|
||||
<item quantity="other">%s Deskarga amaituta</item>
|
||||
</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="local_search_suggestions">Tokiko bilaketa-iradokizunak</string>
|
||||
<string name="mark_as_watched">Ikusi gisa markatu</string>
|
||||
|
|
|
@ -597,7 +597,6 @@
|
|||
<string name="notification_colorize_title">رنگی کردن آگاهی</string>
|
||||
<string name="open_with">گشودن با</string>
|
||||
<string name="mark_as_watched">نشانه به عنوان دیده شده</string>
|
||||
<string name="show_image_indicators_summary">نمایش روبانهای رنگی پیکاسو در بالای تصویرها کهنشانگر منبعشان است: قرمز برای شبکه ، آبی برای دیسک و سبز برای حافظه</string>
|
||||
<string name="notification_colorize_summary">درخواست از اندروید برای سفارشیسازی رنگ آگاهی براساس رنگ اصلی در بندانگشتی (توجّه داشته باشید که روی همهٔ افزارهها در دسترس نیست)</string>
|
||||
<string name="restricted_video">این ویدیو محدود به سن است.
|
||||
\n
|
||||
|
@ -635,7 +634,6 @@
|
|||
<string name="detail_heart_img_view_description">قلبشده به دست ایجادگر</string>
|
||||
<string name="local_search_suggestions">پیشنهادهای جستوجوی محلّی</string>
|
||||
<string name="remote_search_suggestions">پیشنهادهای جستوجوی دوردست</string>
|
||||
<string name="show_image_indicators_title">نمایش نشانگرهای تصویر</string>
|
||||
<plurals name="download_finished_notification">
|
||||
<item quantity="one">بارگیری پایان یافت</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="comments_are_disabled">Kommentit poistettu käytöstä</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="local_search_suggestions">Paikalliset hakuehdotukset</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="mark_as_watched">Marquer comme visionné</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="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">
|
||||
<item quantity="one">%1$s téléchargement supprimé</item>
|
||||
<item quantity="many">%1$s téléchargements supprimés</item>
|
||||
|
|
|
@ -627,7 +627,6 @@
|
|||
<item quantity="other">%s descargas finalizadas</item>
|
||||
</plurals>
|
||||
<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_title">Desactivar túnel multimedia</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="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="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="progressive_load_interval_exoplayer_default">Predefinido do ExoPlayer</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="detail_heart_img_view_description">סומן בלב על ידי היוצר</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="local_search_suggestions">הצעות חיפוש מקומיות</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -570,7 +570,6 @@
|
|||
<string name="enqueued">कतारबद्ध हुआ</string>
|
||||
<string name="loading_stream_details">स्ट्रीम विवरण लोड हो रहे हैं…</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="check_new_streams">नई स्ट्रीमों के लिए जांच चलाएं</string>
|
||||
<string name="show_error_snackbar">एक त्रुटि स्नैकबार दिखाएं</string>
|
||||
|
@ -668,7 +667,6 @@
|
|||
<string name="leak_canary_not_available">लीक-कैनरी उपलब्ध नहीं है</string>
|
||||
<string name="error_report_notification_toast">एक त्रुटी हुई है, नोटीफिकेशन देखें</string>
|
||||
<string name="disable_media_tunneling_summary">यदि वीडियो प्लेबैक पर आप काली स्क्रीन या रुक-रुक कर वीडियो चलने का अनुभव करते हैं तो मीडिया टनलिंग को अक्षम करें।</string>
|
||||
<string name="show_image_indicators_summary">छवियों के शीर्ष पर पिकासो रंगीन रिबन दिखाएँ जो उनके स्रोत को दर्शाता है: नेटवर्क के लिए लाल, डिस्क के लिए नीला और मेमोरी के लिए हरा</string>
|
||||
<string name="create_error_notification">त्रुटी की नोटीफिकेशन बनाएं</string>
|
||||
<string name="error_download_resource_gone">इस डाउनलोड को पुनर्प्राप्त नहीं किया जा सकता</string>
|
||||
<string name="no_dir_yet">अभी तक कोई डाउनलोड फ़ोल्डर सेट नहीं किया गया है, अब डिफ़ॉल्ट डाउनलोड फ़ोल्डर चुनें</string>
|
||||
|
|
|
@ -646,7 +646,6 @@
|
|||
<string name="service_provides_reason">%s pruža ovaj razlog:</string>
|
||||
<string name="processing_may_take_a_moment">Obrada u tijeku … Može malo potrajati</string>
|
||||
<string name="main_page_content_swipe_remove">Za ukljanjanje stavki povuci ih</string>
|
||||
<string name="show_image_indicators_title">Prikaži indikatore slike</string>
|
||||
<plurals name="download_finished_notification">
|
||||
<item quantity="one">Preuzimanje je gotovo</item>
|
||||
<item quantity="few">%s preuzimanja su gotova</item>
|
||||
|
@ -655,7 +654,6 @@
|
|||
<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="enqueued_next">Dodano u popis kao sljedeći</string>
|
||||
<string name="show_image_indicators_summary">Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
<item quantity="one">Izbrisano %1$s preuzimanje</item>
|
||||
<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="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="show_image_indicators_summary">Picasso színes szalagok megjelenítése a képek fölött, megjelölve a forrásukat: piros a hálózathoz, kék a lemezhez, zöld a memóriához</string>
|
||||
<string name="downloads_storage_ask_summary_no_saf_notice">Minden letöltésnél meg fogja kérdezni, hogy hova mentse el</string>
|
||||
<string name="choose_instance_prompt">Válasszon egy példányt</string>
|
||||
<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>
|
||||
<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="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_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>
|
||||
|
|
|
@ -631,13 +631,11 @@
|
|||
<string name="detail_heart_img_view_description">Disukai oleh kreator</string>
|
||||
<string name="local_search_suggestions">Saran pencarian lokal</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">
|
||||
<item quantity="other">Menghapus %1$s unduhan</item>
|
||||
</plurals>
|
||||
<string name="enqueue_next_stream">tambahkan 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="processing_may_take_a_moment">Memproses… Mungkin butuh waktu sebentar</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="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="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_summary">Sýna valkost til að hrynja spilara</string>
|
||||
<string name="crash_the_app">Hrynja forrit</string>
|
||||
|
|
|
@ -644,8 +644,6 @@
|
|||
<string name="comments_are_disabled">Commenti disattivati</string>
|
||||
<string name="detail_heart_img_view_description">Apprezzato dall\'autore</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="local_search_suggestions">Suggerimenti di ricerca locali</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -634,8 +634,6 @@
|
|||
<plurals name="download_finished_notification">
|
||||
<item quantity="other">%s つのダウンロードが完了しました</item>
|
||||
</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="manual_update_description">新しいバージョンを手動で確認します</string>
|
||||
<string name="checking_updates_toast">アップデートを確認中…</string>
|
||||
|
|
|
@ -384,7 +384,6 @@
|
|||
<string name="show_original_time_ago_summary">ორიგინალური ტექსტები სერვისებიდან ხილული იქნება ნაკადის ერთეულებში</string>
|
||||
<string name="disable_media_tunneling_title">მედია გვირაბის გათიშვა</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_summary">აჩვენებს ავარიის ვარიანტს დამკვრელის გამოყენებისას</string>
|
||||
<string name="check_new_streams">გაუშვით შემოწმება ახალი ნაკადებისთვის</string>
|
||||
|
@ -683,7 +682,6 @@
|
|||
<string name="subscriptions_import_unsuccessful">გამოწერების იმპორტი ვერ მოხერხდა</string>
|
||||
<string name="enable_disposed_exceptions_title">შეატყობინეთ სასიცოცხლო ციკლის შეცდომებს</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_account_info">„%s“-ის არხის ჩატვირთვა ვერ მოხერხდა.</string>
|
||||
<string name="feed_use_dedicated_fetch_method_summary">ხელმისაწვდომია ზოგიერთ სერვისში, როგორც წესი, ბევრად უფრო სწრაფია, მაგრამ შეიძლება დააბრუნოს შეზღუდული რაოდენობის ელემენტი და ხშირად არასრული ინფორმაცია (მაგ. ხანგრძლივობის გარეშე, ელემენტის ტიპი, არ არის ლაივის სტატუსი)</string>
|
||||
|
|
|
@ -545,8 +545,6 @@
|
|||
<string name="disable_media_tunneling_title">미디어 터널링 비활성화</string>
|
||||
<string name="show_original_time_ago_summary">서비스의 원본 텍스트가 스트림 항목에 표시됩니다</string>
|
||||
<string name="disable_media_tunneling_summary">동영상 재생 시 검은 화면이 나타나거나 끊김 현상이 발생하면 미디어 터널링을 비활성화하세요.</string>
|
||||
<string name="show_image_indicators_title">이미지 표시기 표시</string>
|
||||
<string name="show_image_indicators_summary">원본을 나타내는 이미지 위에 피카소 컬러 리본 표시: 네트워크는 빨간색, 디스크는 파란색, 메모리는 녹색</string>
|
||||
<string name="show_crash_the_player_title">\"플레이어 충돌\" 표시</string>
|
||||
<string name="show_crash_the_player_summary">플레이어를 사용할 때 충돌 옵션을 표시합니다</string>
|
||||
<string name="check_new_streams">새로운 스트림 확인 실행</string>
|
||||
|
|
|
@ -644,8 +644,6 @@
|
|||
<string name="dont_show">Nerodyti</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="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="local_search_suggestions">Vietinės paieškos pasiūlymai</string>
|
||||
<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="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="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="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="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="check_for_updates">Pārbaudīt atjauninājumus</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_0_title">ആദ്യ പ്രവർത്തന ബട്ടൺ</string>
|
||||
<string name="disable_media_tunneling_summary">വീഡിയോ കാണുമ്പോൾ കറുത്ത സ്ക്രീൻ, അവ്യക്തത അനുഭവിക്കുന്നു എങ്കിൽ മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
||||
<string name="show_image_indicators_summary">ഉറവിടം തിരിച്ചറിയാൻ പിക്കാസോ കളർഡ് റിബൺ ചിത്രങ്ങളുടെ മുകളിൽ കാണിക്കുക: നെറ്റ്വർക്കിന് ചുവപ്പ്, ഡിസ്കിനു നീല, മെമ്മറിയിക്ക് പച്ച</string>
|
||||
<string name="seekbar_preview_thumbnail_title">സീക്ബാർ ചെറുചിത്രം പ്രദർശനം</string>
|
||||
<string name="detail_heart_img_view_description">സ്നേഹത്തോടെ സൃഷ്ടാവ്</string>
|
||||
<string name="description_select_disable">ഡിസ്ക്രിപ്ഷനിലെ ടെക്സ്റ്റ് സെലക്ട് ചെയ്യുവാൻ അനുവദിക്കാതെ ഇരിക്കുക</string>
|
||||
|
@ -630,7 +629,6 @@
|
|||
<string name="dont_show">കാണിക്കരുത്</string>
|
||||
<string name="low_quality_smaller">കുറഞ്ഞ നിലവാരം (ചെറുത് )</string>
|
||||
<string name="high_quality_larger">ഉയർന്ന നിലവാരം (വലിയത് )</string>
|
||||
<string name="show_image_indicators_title">ഇമേജ് ഇൻഡിക്കേറ്ററുകൾ കാണിക്കുക</string>
|
||||
<string name="disable_media_tunneling_title">മീഡിയ ട്യൂൺലിങ് പ്രവർത്തനരഹിതമാക്കുക</string>
|
||||
<string name="no_dir_yet">ഡൌൺലോഡ് ഫോൾഡർ ഇത് വരെയും സെറ്റ് ചെയ്തിട്ടില്ല, സ്ഥിര ഡൌൺലോഡ് ഫോൾഡർ ഇപ്പോൾ തിരഞ്ഞെക്കുക</string>
|
||||
<string name="comments_are_disabled">അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമായിരിക്കുന്നു</string>
|
||||
|
|
|
@ -644,9 +644,7 @@
|
|||
<string name="local_search_suggestions">Lokale søkeforslag</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="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="show_image_indicators_title">Vis bildeindikatorer</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="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="dont_show">Niet tonen</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_summary">Configureer actieve stream melding</string>
|
||||
<string name="notifications">Meldingen</string>
|
||||
|
|
|
@ -632,8 +632,6 @@
|
|||
<string name="low_quality_smaller">Lage kwaliteit (kleiner)</string>
|
||||
<string name="high_quality_larger">Hoge kwaliteit (groter)</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="remote_search_suggestions">Zoeksuggesties op afstand</string>
|
||||
<string name="local_search_suggestions">Lokale zoeksuggesties</string>
|
||||
|
|
|
@ -440,8 +440,6 @@
|
|||
<string name="show_original_time_ago_summary">ߗߋߢߊߟߌ ߟߎ߬ ߞߟߏߜߍ߫ ߓߐߛߎ߲ߡߊ ߟߎ߬ ߦߌ߬ߘߊ߬ߕߐ߫ ߟߋ߬ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ ߘߐ߫</string>
|
||||
<string name="disable_media_tunneling_title">ߞߋߟߋߞߋߟߋ ߟߊ߫ ߝߊߟߊ߲ߓߍߦߊ ߟߊߛߊ߬</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="start">ߞߵߊ߬ ߘߊߡߌ߬ߣߊ߬</string>
|
||||
<string name="comments_are_disabled">ߞߊ߲߬ߞߎߡߊ ߟߎ߬ ߟߊߛߊ߬ߣߍ߲ ߠߋ߬</string>
|
||||
|
|
|
@ -342,7 +342,6 @@
|
|||
<string name="show_original_time_ago_summary">ସେବାଗୁଡିକରୁ ମୂଳ ଲେଖା ଷ୍ଟ୍ରିମ୍ ଆଇଟମ୍ ଗୁଡିକରେ ଦୃଶ୍ୟମାନ ହେବ</string>
|
||||
<string name="disable_media_tunneling_title">ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ</string>
|
||||
<string name="disable_media_tunneling_summary">ଯଦି ଆପଣ ଏକ କଳା ପରଦା ଅନୁଭବ କରନ୍ତି କିମ୍ବା ଭିଡିଓ ପ୍ଲେବେକ୍ ଉପରେ ଝୁଣ୍ଟି ପଡ଼ନ୍ତି ତେବେ ମିଡିଆ ଟନେଲିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ ।</string>
|
||||
<string name="show_image_indicators_summary">ଚିତ୍ରଗୁଡ଼ିକର ଉପରେ ପିକାସୋ ରଙ୍ଗୀନ ଫିତା ଦେଖାନ୍ତୁ: ସେମାନଙ୍କର ଉତ୍ସକୁ ସୂଚାଇଥାଏ: ନେଟୱାର୍କ ପାଇଁ ନାଲି, ଡିସ୍କ ପାଇଁ ନୀଳ ଏବଂ ସ୍ମୃତି ପାଇଁ ସବୁଜ</string>
|
||||
<string name="import_title">ଆମଦାନି କରନ୍ତୁ</string>
|
||||
<string name="import_from">ଠାରୁ ଆମଦାନୀ କରନ୍ତୁ</string>
|
||||
<string name="import_ongoing">ଆମଦାନି…</string>
|
||||
|
@ -657,7 +656,6 @@
|
|||
<string name="playlist_no_uploader">ଅଟୋ-ଜେନେରେଟ୍ (କୌଣସି ଅପଲୋଡର୍ ମିଳିଲା ନାହିଁ)</string>
|
||||
<string name="resize_fill">ପୁରଣ କରନ୍ତୁ</string>
|
||||
<string name="caption_setting_title">କ୍ୟାପସନ୍</string>
|
||||
<string name="show_image_indicators_title">ପ୍ରତିଛବି ସୂଚକ ଦେଖାନ୍ତୁ</string>
|
||||
<string name="show_crash_the_player_summary">ପ୍ଲେୟାର ବ୍ୟବହାର କରିବା ସମୟରେ ଏକ କ୍ରାସ୍ ବିକଳ୍ପ ଦେଖାଏ</string>
|
||||
<string name="check_new_streams">ନୂତନ ଷ୍ଟ୍ରିମ୍ ପାଇଁ ଯାଞ୍ଚ ଚଲାନ୍ତୁ</string>
|
||||
<string name="crash_the_app">ଆପ୍ କ୍ରାସ୍ କରନ୍ତୁ</string>
|
||||
|
|
|
@ -626,8 +626,6 @@
|
|||
<string name="error_report_notification_title">ਨਿਊਪਾਈਪ ਖਾਮੀ ਤੋਂ ਪ੍ਰਭਾਵਤ ਹੋਈ ਹੈ, ਇੱਥੇ ਨੱਪ ਕੇ ਰਿਪੋਰਟ ਕਰੋ</string>
|
||||
<string name="error_report_notification_toast">ਇੱਕ ਖਾਮੀ ਪ੍ਰਭਾਵੀ ਹੋਈ ਹੈ, ਨੋਟੀਫੀਕੇਸ਼ਨ ਵੇਖੋ</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="detail_pinned_comment_view_description">ਪਿੰਨ ਕੀਤੀ ਟਿੱਪਣੀ</string>
|
||||
<string name="checking_updates_toast">ਅੱਪਡੇਟ ਦੀ ਉਪਲੱਬਧਤਾ ਪਰਖੀ ਜਾ ਰਹੀ…</string>
|
||||
|
|
|
@ -649,8 +649,6 @@
|
|||
<string name="dont_show">Nie pokazuj</string>
|
||||
<string name="detail_heart_img_view_description">Serduszko od twórcy</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="local_search_suggestions">Lokalne podpowiedzi wyszukiwania</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -644,7 +644,6 @@
|
|||
<string name="comments_are_disabled">Os comentários estão desabilitados</string>
|
||||
<string name="mark_as_watched">Marcar como assistido</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">
|
||||
<item quantity="one">%1$s download excluído</item>
|
||||
<item quantity="many">%1$s downloads excluídos</item>
|
||||
|
@ -655,7 +654,6 @@
|
|||
<item quantity="many">%s downloads concluídos</item>
|
||||
<item quantity="other">%s downloads concluídos</item>
|
||||
</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="enqueue_next_stream">Enfileira a próxima</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="comments_are_disabled">Comentários estão desativados</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="local_search_suggestions">Sugestões de pesquisa locais</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -644,8 +644,6 @@
|
|||
<string name="low_quality_smaller">Baixa qualidade (menor)</string>
|
||||
<string name="high_quality_larger">Alta qualidade (maior)</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="local_search_suggestions">Sugestões de pesquisa locais</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -660,8 +660,6 @@
|
|||
<string name="low_quality_smaller">Calitate scăzută (mai mică)</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="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="processing_may_take_a_moment">Procesarea.. Poate dura un moment</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="detail_heart_img_view_description">Автору видео понравилось это</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="local_search_suggestions">Локальные предложения поиска</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -646,8 +646,6 @@
|
|||
<item quantity="one">%sちぬダウンロードぬかんりょうさびたん</item>
|
||||
<item quantity="other">%sちぬダウンロードぬかんりょうさびたん</item>
|
||||
</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="manual_update_description">みーさるバージョンしーゅどうでぃかくにんさびーん</string>
|
||||
<string name="checking_updates_toast">アップデートかくにんちゅう…</string>
|
||||
|
|
|
@ -270,7 +270,6 @@
|
|||
<string name="leak_canary_not_available">LeakCanary ᱵᱟᱭ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ</string>
|
||||
<string name="enable_leak_canary_summary">ᱢᱮᱢᱚᱨᱤ ᱞᱤᱠᱟᱞ ᱢᱚᱱᱤᱴᱚᱨᱤᱝ ᱦᱤᱯ ᱰᱟᱢᱯᱤᱝ ᱚᱠᱛᱚ ᱨᱮ ᱮᱯᱞᱤᱠᱮᱥᱚᱱ ᱨᱟᱥᱴᱨᱤᱭ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ</string>
|
||||
<string name="enable_disposed_exceptions_title">ᱡᱤᱭᱚᱱ ᱪᱤᱠᱤ ᱠᱷᱚᱱ ᱵᱟᱦᱨᱮ ᱨᱮ ᱵᱷᱮᱜᱟᱨ ᱠᱚ ᱚᱱᱚᱞ ᱢᱮ</string>
|
||||
<string name="show_image_indicators_summary">ᱱᱮᱴᱣᱟᱨᱠ ᱞᱟᱹᱜᱤᱫ red, ᱰᱤᱥᱠ ᱞᱟᱹᱜᱤᱫ blue ᱟᱨ ᱢᱮᱢᱚᱨᱤ ᱞᱟᱹᱜᱤᱫ green</string>
|
||||
<string name="show_crash_the_player_summary">ᱯᱞᱮᱭᱟᱨ ᱵᱮᱵᱷᱟᱨ ᱚᱠᱛᱮ ᱨᱮ ᱠᱨᱟᱥ ᱚᱯᱥᱚᱱ ᱧᱮᱞᱚᱜ ᱠᱟᱱᱟ</string>
|
||||
<string name="import_title">ᱤᱢᱯᱳᱨᱴ</string>
|
||||
<string name="import_from">ᱤᱢᱯᱚᱨᱴ</string>
|
||||
|
@ -540,7 +539,6 @@
|
|||
<string name="show_original_time_ago_title">ᱡᱤᱱᱤᱥ ᱠᱚᱨᱮᱱᱟᱜ ᱢᱩᱞ ᱚᱠᱛᱚ ᱧᱮᱞ ᱢᱮ</string>
|
||||
<string name="show_original_time_ago_summary">ᱥᱮᱵᱟ ᱠᱷᱚᱱ ᱚᱨᱡᱤᱱᱤᱭᱟᱞ ᱴᱮᱠᱥᱴ ᱠᱚ ᱥᱴᱨᱤᱢ ᱤᱴᱮᱢ ᱨᱮ ᱧᱮᱞᱚᱜᱼᱟ</string>
|
||||
<string name="disable_media_tunneling_summary">ᱡᱩᱫᱤ ᱟᱢ ᱵᱷᱤᱰᱤᱭᱳ ᱯᱞᱮᱭᱚᱯ ᱨᱮ ᱵᱞᱮᱠ ᱥᱠᱨᱤᱱ ᱟᱨᱵᱟᱝ ᱠᱷᱟᱹᱞᱤ ᱥᱴᱮᱴᱞᱤᱝ ᱮᱢ ᱧᱟᱢᱟ ᱮᱱᱠᱷᱟᱱ ᱢᱤᱰᱤᱭᱟ ᱴᱩᱱᱮᱞᱤᱝ ᱵᱚᱫᱚᱞ ᱢᱮ ᱾</string>
|
||||
<string name="show_image_indicators_title">ᱪᱤᱛᱟᱹᱨ ᱪᱤᱱᱦᱟᱹ ᱠᱚ ᱧᱮᱞ ᱢᱮ</string>
|
||||
<string name="check_new_streams">ᱱᱟᱣᱟ ᱥᱴᱨᱤᱢ ᱞᱟᱹᱜᱤᱫ ᱪᱟᱪᱞᱟᱣ ᱢᱮ</string>
|
||||
<string name="create_error_notification">ᱢᱤᱫ error notification ᱛᱮᱭᱟᱨ ᱢᱮ</string>
|
||||
<string name="subscriptions_export_unsuccessful">ᱥᱮᱞᱮᱫ ᱮᱠᱥᱯᱳᱨᱴ ᱵᱟᱝ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜ ᱠᱟᱱᱟ</string>
|
||||
|
|
|
@ -634,8 +634,6 @@
|
|||
<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="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="local_search_suggestions">Impòsitos de chirca locales</string>
|
||||
<plurals name="deleted_downloads_toast">
|
||||
|
|
|
@ -653,8 +653,6 @@
|
|||
<string name="low_quality_smaller">Nízka kvalita (menšie)</string>
|
||||
<string name="high_quality_larger">Vysoká kvalita (väčšie)</string>
|
||||
<string name="seekbar_preview_thumbnail_title">Náhľad miniatúry pri vyhľadávaní</string>
|
||||
<string name="show_image_indicators_summary">Zobrazí farebné pásiky Picasso na obrázkoch podľa ich zdroja: červený pre sieť, modrý pre disk a zelený pre pamäť</string>
|
||||
<string name="show_image_indicators_title">Zobraziť indikátory obrázka</string>
|
||||
<string name="main_page_content_swipe_remove">Potiahnutím vymazať</string>
|
||||
<string name="comments_are_disabled">Komentáre sú zakázané</string>
|
||||
<string name="start_main_player_fullscreen_summary">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="detail_heart_img_view_description">Kahelay soosaaraha</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="local_search_suggestions">Soojeedinada raadinta gudaha</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