mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2025-10-03 09:49:21 +02:00
Merge e4c7e4b241
into 965eea2124
This commit is contained in:
commit
910ad17422
35 changed files with 208 additions and 271 deletions
|
@ -214,7 +214,7 @@ dependencies {
|
||||||
// the corresponding commit hash, since JitPack sometimes deletes artifacts.
|
// the corresponding commit hash, since JitPack sometimes deletes artifacts.
|
||||||
// If there’s already a git hash, just add more of it to the end (or remove a letter)
|
// If there’s already a git hash, just add more of it to the end (or remove a letter)
|
||||||
// to cause jitpack to regenerate the artifact.
|
// to cause jitpack to regenerate the artifact.
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:0023b22095a2d62a60cdfc87f4b5cd85c8b266c3'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:c79afc00764a5f2deb8f73e78244d93920cb3456'
|
||||||
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
|
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
|
||||||
|
|
||||||
/** Checkstyle **/
|
/** Checkstyle **/
|
||||||
|
@ -229,6 +229,7 @@ dependencies {
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
|
implementation 'androidx.core:core-i18n:1.0.0'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
|
||||||
|
|
|
@ -144,7 +144,7 @@ class DatabaseMigrationTest {
|
||||||
assertEquals(DEFAULT_THUMBNAIL, streamFromMigratedDatabase.thumbnailUrl)
|
assertEquals(DEFAULT_THUMBNAIL, streamFromMigratedDatabase.thumbnailUrl)
|
||||||
assertNull(streamFromMigratedDatabase.viewCount)
|
assertNull(streamFromMigratedDatabase.viewCount)
|
||||||
assertNull(streamFromMigratedDatabase.textualUploadDate)
|
assertNull(streamFromMigratedDatabase.textualUploadDate)
|
||||||
assertNull(streamFromMigratedDatabase.uploadDate)
|
assertNull(streamFromMigratedDatabase.uploadInstant)
|
||||||
assertNull(streamFromMigratedDatabase.isUploadDateApproximation)
|
assertNull(streamFromMigratedDatabase.isUploadDateApproximation)
|
||||||
|
|
||||||
val secondStreamFromMigratedDatabase = listFromDB[1]
|
val secondStreamFromMigratedDatabase = listFromDB[1]
|
||||||
|
@ -158,7 +158,7 @@ class DatabaseMigrationTest {
|
||||||
assertEquals("", secondStreamFromMigratedDatabase.thumbnailUrl)
|
assertEquals("", secondStreamFromMigratedDatabase.thumbnailUrl)
|
||||||
assertNull(secondStreamFromMigratedDatabase.viewCount)
|
assertNull(secondStreamFromMigratedDatabase.viewCount)
|
||||||
assertNull(secondStreamFromMigratedDatabase.textualUploadDate)
|
assertNull(secondStreamFromMigratedDatabase.textualUploadDate)
|
||||||
assertNull(secondStreamFromMigratedDatabase.uploadDate)
|
assertNull(secondStreamFromMigratedDatabase.uploadInstant)
|
||||||
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
|
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,31 +4,27 @@ import androidx.room.TypeConverter
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
/**
|
/**
|
||||||
* Convert a long value to a [OffsetDateTime].
|
* Convert a long value to an [Instant].
|
||||||
*
|
*
|
||||||
* @param value the long value
|
* @param value the long value
|
||||||
* @return the `OffsetDateTime`
|
* @return the `Instant`
|
||||||
*/
|
*/
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun offsetDateTimeFromTimestamp(value: Long?): OffsetDateTime? {
|
fun timestampToInstant(value: Long?): Instant? {
|
||||||
return value?.let { OffsetDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC) }
|
return value?.let { Instant.ofEpochMilli(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a [OffsetDateTime] to a long value.
|
* Convert an [Instant] to a long value.
|
||||||
*
|
*
|
||||||
* @param offsetDateTime the `OffsetDateTime`
|
* @param instant the `Instant`
|
||||||
* @return the long value
|
* @return the long value
|
||||||
*/
|
*/
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun offsetDateTimeToTimestamp(offsetDateTime: OffsetDateTime?): Long? {
|
fun instantToTimestamp(instant: Instant?) = instant?.toEpochMilli()
|
||||||
return offsetDateTime?.withOffsetSameInstant(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun streamTypeOf(value: String): StreamType {
|
fun streamTypeOf(value: String): StreamType {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.schabi.newpipe.database.stream.StreamWithState
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
||||||
import org.schabi.newpipe.database.subscription.NotificationMode
|
import org.schabi.newpipe.database.subscription.NotificationMode
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class FeedDAO {
|
abstract class FeedDAO {
|
||||||
|
@ -90,7 +90,7 @@ abstract class FeedDAO {
|
||||||
groupId: Long,
|
groupId: Long,
|
||||||
includePlayed: Boolean,
|
includePlayed: Boolean,
|
||||||
includePartiallyPlayed: Boolean,
|
includePartiallyPlayed: Boolean,
|
||||||
uploadDateBefore: OffsetDateTime?
|
uploadDateBefore: Instant?
|
||||||
): Maybe<List<StreamWithState>>
|
): Maybe<List<StreamWithState>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,7 +99,7 @@ abstract class FeedDAO {
|
||||||
*
|
*
|
||||||
* One stream per uploader is kept because it is needed as reference
|
* One stream per uploader is kept because it is needed as reference
|
||||||
* when fetching new streams to check if they are new or not.
|
* when fetching new streams to check if they are new or not.
|
||||||
* @param offsetDateTime the newest date to keep, older streams are removed
|
* @param instant the newest date to keep, older streams are removed
|
||||||
*/
|
*/
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -115,11 +115,11 @@ abstract class FeedDAO {
|
||||||
INNER JOIN feed f
|
INNER JOIN feed f
|
||||||
ON s.uid = f.stream_id
|
ON s.uid = f.stream_id
|
||||||
|
|
||||||
WHERE s.upload_date < :offsetDateTime
|
WHERE s.upload_date < :instant
|
||||||
AND s.upload_date <> max_upload_date))
|
AND s.upload_date <> max_upload_date))
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
|
abstract fun unlinkStreamsOlderThan(instant: Instant)
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -168,13 +168,13 @@ abstract class FeedDAO {
|
||||||
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
|
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
|
abstract fun getOldestSubscriptionUpdate(groupId: Long): Flowable<List<Instant>>
|
||||||
|
|
||||||
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
|
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
|
||||||
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
|
abstract fun getOldestSubscriptionUpdateFromAll(): Flowable<List<Instant>>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
|
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
|
||||||
abstract fun notLoadedCount(): Flowable<Long>
|
abstract fun getNotLoadedCount(): Flowable<Long>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +189,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL
|
WHERE lu.last_updated IS NULL
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
|
abstract fun getNotLoadedCountForGroup(groupId: Long): Flowable<Long>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -201,7 +201,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
|
abstract fun getAllOutdated(outdatedThreshold: Instant): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -216,7 +216,7 @@ abstract class FeedDAO {
|
||||||
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
|
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: Instant): Flowable<List<SubscriptionEntity>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@ -231,7 +231,7 @@ abstract class FeedDAO {
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getOutdatedWithNotificationMode(
|
abstract fun getOutdatedWithNotificationMode(
|
||||||
outdatedThreshold: OffsetDateTime,
|
outdatedThreshold: Instant,
|
||||||
@NotificationMode notificationMode: Int
|
@NotificationMode notificationMode: Int
|
||||||
): Flowable<List<SubscriptionEntity>>
|
): Flowable<List<SubscriptionEntity>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import androidx.room.PrimaryKey
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = FEED_LAST_UPDATED_TABLE,
|
tableName = FEED_LAST_UPDATED_TABLE,
|
||||||
|
@ -26,7 +26,7 @@ data class FeedLastUpdatedEntity(
|
||||||
var subscriptionId: Long,
|
var subscriptionId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = LAST_UPDATED)
|
@ColumnInfo(name = LAST_UPDATED)
|
||||||
var lastUpdated: OffsetDateTime? = null
|
var lastUpdated: Instant? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val FEED_LAST_UPDATED_TABLE = "feed_last_updated"
|
const val FEED_LAST_UPDATED_TABLE = "feed_last_updated"
|
||||||
|
|
|
@ -5,18 +5,16 @@ import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = SearchHistoryEntry.TABLE_NAME,
|
tableName = SearchHistoryEntry.TABLE_NAME,
|
||||||
indices = [Index(value = [SearchHistoryEntry.SEARCH])]
|
indices = [Index(value = [SearchHistoryEntry.SEARCH])]
|
||||||
)
|
)
|
||||||
data class SearchHistoryEntry(
|
data class SearchHistoryEntry(
|
||||||
@field:ColumnInfo(name = CREATION_DATE) var creationDate: OffsetDateTime?,
|
@ColumnInfo(name = CREATION_DATE) var creationInstant: Instant?,
|
||||||
@field:ColumnInfo(
|
@ColumnInfo(name = SERVICE_ID) var serviceId: Int,
|
||||||
name = SERVICE_ID
|
@ColumnInfo(name = SEARCH) var search: String?
|
||||||
) var serviceId: Int,
|
|
||||||
@field:ColumnInfo(name = SEARCH) var search: String?
|
|
||||||
) {
|
) {
|
||||||
@ColumnInfo(name = ID)
|
@ColumnInfo(name = ID)
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ -24,10 +22,7 @@ data class SearchHistoryEntry(
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean {
|
fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean {
|
||||||
return (
|
return serviceId == otherEntry.serviceId && search == otherEntry.search
|
||||||
serviceId == otherEntry.serviceId &&
|
|
||||||
search == otherEntry.search
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import androidx.room.Index;
|
||||||
|
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.Instant;
|
||||||
|
|
||||||
import static androidx.room.ForeignKey.CASCADE;
|
import static androidx.room.ForeignKey.CASCADE;
|
||||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||||
|
@ -36,21 +36,21 @@ public class StreamHistoryEntity {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ColumnInfo(name = STREAM_ACCESS_DATE)
|
@ColumnInfo(name = STREAM_ACCESS_DATE)
|
||||||
private OffsetDateTime accessDate;
|
private Instant accessInstant;
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_REPEAT_COUNT)
|
@ColumnInfo(name = STREAM_REPEAT_COUNT)
|
||||||
private long repeatCount;
|
private long repeatCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param streamUid the stream id this history item will refer to
|
* @param streamUid the stream id this history item will refer to
|
||||||
* @param accessDate the last time the stream was accessed
|
* @param accessInstant the last time the stream was accessed
|
||||||
* @param repeatCount the total number of views this stream received
|
* @param repeatCount the total number of views this stream received
|
||||||
*/
|
*/
|
||||||
public StreamHistoryEntity(final long streamUid,
|
public StreamHistoryEntity(final long streamUid,
|
||||||
@NonNull final OffsetDateTime accessDate,
|
@NonNull final Instant accessInstant,
|
||||||
final long repeatCount) {
|
final long repeatCount) {
|
||||||
this.streamUid = streamUid;
|
this.streamUid = streamUid;
|
||||||
this.accessDate = accessDate;
|
this.accessInstant = accessInstant;
|
||||||
this.repeatCount = repeatCount;
|
this.repeatCount = repeatCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,12 +63,12 @@ public class StreamHistoryEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public OffsetDateTime getAccessDate() {
|
public Instant getAccessInstant() {
|
||||||
return accessDate;
|
return accessInstant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccessDate(@NonNull final OffsetDateTime accessDate) {
|
public void setAccessInstant(@NonNull final Instant accessInstant) {
|
||||||
this.accessDate = accessDate;
|
this.accessInstant = accessInstant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getRepeatCount() {
|
public long getRepeatCount() {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import androidx.room.Embedded
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
data class StreamHistoryEntry(
|
data class StreamHistoryEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
|
@ -15,21 +15,11 @@ data class StreamHistoryEntry(
|
||||||
val streamId: Long,
|
val streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||||
val accessDate: OffsetDateTime,
|
val accessInstant: Instant,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||||
val repeatCount: Long
|
val repeatCount: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toStreamHistoryEntity(): StreamHistoryEntity {
|
|
||||||
return StreamHistoryEntity(streamId, accessDate, repeatCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasEqualValues(other: StreamHistoryEntry): Boolean {
|
|
||||||
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
|
|
||||||
accessDate.isEqual(other.accessDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toStreamInfoItem(): StreamInfoItem =
|
fun toStreamInfoItem(): StreamInfoItem =
|
||||||
StreamInfoItem(
|
StreamInfoItem(
|
||||||
streamEntity.serviceId,
|
streamEntity.serviceId,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
class StreamStatisticsEntry(
|
class StreamStatisticsEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
|
@ -21,7 +21,7 @@ class StreamStatisticsEntry(
|
||||||
val streamId: Long,
|
val streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||||
val latestAccessDate: OffsetDateTime,
|
val latestAccessInstant: Instant,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||||
val watchCount: Long
|
val watchCount: Long
|
||||||
|
|
|
@ -13,7 +13,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil
|
import org.schabi.newpipe.util.StreamTypeUtil
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
|
@ -95,9 +95,9 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
// Use the existent upload date if the newer stream does not have a better precision
|
// Use the existent upload date if the newer stream does not have a better precision
|
||||||
// (i.e. is an approximation). This is done to prevent unnecessary changes.
|
// (i.e. is an approximation). This is done to prevent unnecessary changes.
|
||||||
val hasBetterPrecision =
|
val hasBetterPrecision =
|
||||||
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
|
newerStream.uploadInstant != null && newerStream.isUploadDateApproximation != true
|
||||||
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
|
if (existentMinimalStream.uploadInstant != null && !hasBetterPrecision) {
|
||||||
newerStream.uploadDate = existentMinimalStream.uploadDate
|
newerStream.uploadInstant = existentMinimalStream.uploadInstant
|
||||||
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
|
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
|
||||||
newerStream.isUploadDateApproximation = existentMinimalStream.isUploadDateApproximation
|
newerStream.isUploadDateApproximation = existentMinimalStream.isUploadDateApproximation
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
var textualUploadDate: String? = null,
|
var textualUploadDate: String? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||||
var uploadDate: OffsetDateTime? = null,
|
var uploadInstant: Instant? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||||
var isUploadDateApproximation: Boolean? = null,
|
var isUploadDateApproximation: Boolean? = null,
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = STREAM_TABLE,
|
tableName = STREAM_TABLE,
|
||||||
|
@ -59,7 +59,7 @@ data class StreamEntity(
|
||||||
var textualUploadDate: String? = null,
|
var textualUploadDate: String? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||||
var uploadDate: OffsetDateTime? = null,
|
var uploadInstant: Instant? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||||
var isUploadDateApproximation: Boolean? = null
|
var isUploadDateApproximation: Boolean? = null
|
||||||
|
@ -69,8 +69,9 @@ data class StreamEntity(
|
||||||
serviceId = item.serviceId, url = item.url, title = item.name,
|
serviceId = item.serviceId, url = item.url, title = item.name,
|
||||||
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
|
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
|
||||||
uploaderUrl = item.uploaderUrl,
|
uploaderUrl = item.uploaderUrl,
|
||||||
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails), viewCount = item.viewCount,
|
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails),
|
||||||
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
|
viewCount = item.viewCount, textualUploadDate = item.textualUploadDate,
|
||||||
|
uploadInstant = item.uploadDate?.instant,
|
||||||
isUploadDateApproximation = item.uploadDate?.isApproximation
|
isUploadDateApproximation = item.uploadDate?.isApproximation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,8 +80,9 @@ data class StreamEntity(
|
||||||
serviceId = info.serviceId, url = info.url, title = info.name,
|
serviceId = info.serviceId, url = info.url, title = info.name,
|
||||||
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
|
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
|
||||||
uploaderUrl = info.uploaderUrl,
|
uploaderUrl = info.uploaderUrl,
|
||||||
thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails), viewCount = info.viewCount,
|
thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails),
|
||||||
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
|
viewCount = info.viewCount,
|
||||||
|
textualUploadDate = info.textualUploadDate, uploadInstant = info.uploadDate?.instant,
|
||||||
isUploadDateApproximation = info.uploadDate?.isApproximation
|
isUploadDateApproximation = info.uploadDate?.isApproximation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,7 +103,7 @@ data class StreamEntity(
|
||||||
|
|
||||||
if (viewCount != null) item.viewCount = viewCount as Long
|
if (viewCount != null) item.viewCount = viewCount as Long
|
||||||
item.textualUploadDate = textualUploadDate
|
item.textualUploadDate = textualUploadDate
|
||||||
item.uploadDate = uploadDate?.let {
|
item.uploadDate = uploadInstant?.let {
|
||||||
DateWrapper(it, isUploadDateApproximation ?: false)
|
DateWrapper(it, isUploadDateApproximation ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,11 +65,12 @@ public class DescriptionFragment extends BaseDescriptionFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setupMetadata(final LayoutInflater inflater,
|
protected void setupMetadata(final LayoutInflater inflater, final LinearLayout layout) {
|
||||||
final LinearLayout layout) {
|
final var uploadDate = streamInfo != null ? streamInfo.getUploadDate() : null;
|
||||||
if (streamInfo != null && streamInfo.getUploadDate() != null) {
|
if (uploadDate != null) {
|
||||||
binding.detailUploadDateView.setText(Localization
|
final String formattedDate = activity.getString(R.string.upload_date_text,
|
||||||
.localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime()));
|
Localization.formatDate(uploadDate));
|
||||||
|
binding.detailUploadDateView.setText(formattedDate);
|
||||||
} else {
|
} else {
|
||||||
binding.detailUploadDateView.setVisibility(View.GONE);
|
binding.detailUploadDateView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ public final class CommentRepliesFragment
|
||||||
|
|
||||||
// setup author name and comment date
|
// setup author name and comment date
|
||||||
binding.authorName.setText(item.getUploaderName());
|
binding.authorName.setText(item.getUploaderName());
|
||||||
binding.uploadDate.setText(Localization.relativeTimeOrTextual(
|
binding.uploadDate.setText(Localization.formatRelativeTimeOrTextual(
|
||||||
getContext(), item.getUploadDate(), item.getTextualUploadDate()));
|
getContext(), item.getUploadDate(), item.getTextualUploadDate()));
|
||||||
binding.authorTouchArea.setOnClickListener(
|
binding.authorTouchArea.setOnClickListener(
|
||||||
v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
|
v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
|
||||||
|
|
|
@ -82,11 +82,9 @@ public class CommentInfoItemHolder extends InfoItemHolder {
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final InfoItem infoItem,
|
public void updateFromItem(final InfoItem infoItem,
|
||||||
final HistoryRecordManager historyRecordManager) {
|
final HistoryRecordManager historyRecordManager) {
|
||||||
if (!(infoItem instanceof CommentsInfoItem)) {
|
if (!(infoItem instanceof CommentsInfoItem item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
|
||||||
|
|
||||||
|
|
||||||
// load the author avatar
|
// load the author avatar
|
||||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
|
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
|
||||||
|
@ -104,12 +102,9 @@ public class CommentInfoItemHolder extends InfoItemHolder {
|
||||||
// setup the top row, with pinned icon, author name and comment date
|
// setup the top row, with pinned icon, author name and comment date
|
||||||
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
|
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
|
||||||
final String uploaderName = Localization.localizeUserName(item.getUploaderName());
|
final String uploaderName = Localization.localizeUserName(item.getUploaderName());
|
||||||
itemTitleView.setText(Localization.concatenateStrings(
|
final String relativeTime = Localization.formatRelativeTimeOrTextual(
|
||||||
uploaderName,
|
itemBuilder.getContext(), item.getUploadDate(), item.getTextualUploadDate());
|
||||||
Localization.relativeTimeOrTextual(
|
itemTitleView.setText(Localization.concatenateStrings(uploaderName, relativeTime));
|
||||||
itemBuilder.getContext(),
|
|
||||||
item.getUploadDate(),
|
|
||||||
item.getTextualUploadDate())));
|
|
||||||
|
|
||||||
// setup bottom row, with likes, heart and replies button
|
// setup bottom row, with likes, heart and replies button
|
||||||
itemLikesCountView.setText(
|
itemLikesCountView.setText(
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String uploadDate = Localization.relativeTimeOrTextual(itemBuilder.getContext(),
|
final String uploadDate = Localization.formatRelativeTimeOrTextual(itemBuilder.getContext(),
|
||||||
infoItem.getUploadDate(),
|
infoItem.getUploadDate(),
|
||||||
infoItem.getTextualUploadDate());
|
infoItem.getTextualUploadDate());
|
||||||
if (!TextUtils.isEmpty(uploadDate)) {
|
if (!TextUtils.isEmpty(uploadDate)) {
|
||||||
|
|
|
@ -30,11 +30,8 @@ import org.schabi.newpipe.local.holder.RemotePlaylistCardItemHolder;
|
||||||
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
|
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
|
||||||
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
|
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
|
||||||
import org.schabi.newpipe.util.FallbackViewHolder;
|
import org.schabi.newpipe.util.FallbackViewHolder;
|
||||||
import org.schabi.newpipe.util.Localization;
|
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -85,7 +82,6 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
private final LocalItemBuilder localItemBuilder;
|
private final LocalItemBuilder localItemBuilder;
|
||||||
private final ArrayList<LocalItem> localItems;
|
private final ArrayList<LocalItem> localItems;
|
||||||
private final HistoryRecordManager recordManager;
|
private final HistoryRecordManager recordManager;
|
||||||
private final DateTimeFormatter dateTimeFormatter;
|
|
||||||
|
|
||||||
private boolean showFooter = false;
|
private boolean showFooter = false;
|
||||||
private View header = null;
|
private View header = null;
|
||||||
|
@ -97,8 +93,6 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
recordManager = new HistoryRecordManager(context);
|
recordManager = new HistoryRecordManager(context);
|
||||||
localItemBuilder = new LocalItemBuilder(context);
|
localItemBuilder = new LocalItemBuilder(context);
|
||||||
localItems = new ArrayList<>();
|
localItems = new ArrayList<>();
|
||||||
dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
|
||||||
.withLocale(Localization.getPreferredLocale(context));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
|
public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
|
||||||
|
@ -364,19 +358,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||||
+ "position = [" + position + "]");
|
+ "position = [" + position + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (holder instanceof LocalItemHolder) {
|
if (holder instanceof LocalItemHolder localItemHolder) {
|
||||||
// If header isn't null, offset the items by -1
|
// If header isn't null, offset the items by -1
|
||||||
if (header != null) {
|
if (header != null) {
|
||||||
position--;
|
position--;
|
||||||
}
|
}
|
||||||
|
|
||||||
((LocalItemHolder) holder)
|
localItemHolder.updateFromItem(localItems.get(position), recordManager);
|
||||||
.updateFromItem(localItems.get(position), recordManager, dateTimeFormatter);
|
} else if (holder instanceof HeaderFooterHolder headerFooterHolder) {
|
||||||
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
|
if (position == 0 && header != null) {
|
||||||
((HeaderFooterHolder) holder).view = header;
|
headerFooterHolder.view = header;
|
||||||
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
|
} else if (footer != null && showFooter) {
|
||||||
&& footer != null && showFooter) {
|
headerFooterHolder.view = footer;
|
||||||
((HeaderFooterHolder) holder).view = footer;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ import org.schabi.newpipe.database.subscription.NotificationMode
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.OffsetDateTime
|
import java.time.ZoneId
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
class FeedDatabaseManager(context: Context) {
|
class FeedDatabaseManager(context: Context) {
|
||||||
private val database = NewPipeDatabase.getInstance(context)
|
private val database = NewPipeDatabase.getInstance(context)
|
||||||
|
@ -28,14 +28,6 @@ class FeedDatabaseManager(context: Context) {
|
||||||
private val feedGroupTable = database.feedGroupDAO()
|
private val feedGroupTable = database.feedGroupDAO()
|
||||||
private val streamTable = database.streamDAO()
|
private val streamTable = database.streamDAO()
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Only items that are newer than this will be saved.
|
|
||||||
*/
|
|
||||||
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
|
|
||||||
.atStartOfDay().atOffset(ZoneOffset.UTC)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun groups() = feedGroupTable.getAll()
|
fun groups() = feedGroupTable.getAll()
|
||||||
|
|
||||||
fun database() = database
|
fun database() = database
|
||||||
|
@ -50,27 +42,27 @@ class FeedDatabaseManager(context: Context) {
|
||||||
groupId,
|
groupId,
|
||||||
includePlayedStreams,
|
includePlayedStreams,
|
||||||
includePartiallyPlayedStreams,
|
includePartiallyPlayedStreams,
|
||||||
if (includeFutureStreams) null else OffsetDateTime.now()
|
if (includeFutureStreams) null else Instant.now()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold)
|
fun getOutdatedSubscriptions(outdatedThreshold: Instant) = feedTable.getAllOutdated(outdatedThreshold)
|
||||||
|
|
||||||
fun outdatedSubscriptionsWithNotificationMode(
|
fun getOutdatedSubscriptionsWithNotificationMode(
|
||||||
outdatedThreshold: OffsetDateTime,
|
outdatedThreshold: Instant,
|
||||||
@NotificationMode notificationMode: Int
|
@NotificationMode notificationMode: Int
|
||||||
) = feedTable.getOutdatedWithNotificationMode(outdatedThreshold, notificationMode)
|
) = feedTable.getOutdatedWithNotificationMode(outdatedThreshold, notificationMode)
|
||||||
|
|
||||||
fun notLoadedCount(groupId: Long = FeedGroupEntity.GROUP_ALL_ID): Flowable<Long> {
|
fun getNotLoadedCount(groupId: Long = FeedGroupEntity.GROUP_ALL_ID): Flowable<Long> {
|
||||||
return when (groupId) {
|
return when (groupId) {
|
||||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.notLoadedCount()
|
FeedGroupEntity.GROUP_ALL_ID -> feedTable.getNotLoadedCount()
|
||||||
else -> feedTable.notLoadedCountForGroup(groupId)
|
else -> feedTable.getNotLoadedCountForGroup(groupId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun outdatedSubscriptionsForGroup(
|
fun getOutdatedSubscriptionsForGroup(
|
||||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
outdatedThreshold: OffsetDateTime
|
outdatedThreshold: Instant
|
||||||
) = feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
|
) = feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
|
||||||
|
|
||||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||||
|
@ -80,20 +72,13 @@ class FeedDatabaseManager(context: Context) {
|
||||||
return streamTable.exists(stream.serviceId, stream.url)
|
return streamTable.exists(stream.serviceId, stream.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsertAll(
|
fun upsertAll(subscriptionId: Long, items: List<StreamInfoItem>) {
|
||||||
subscriptionId: Long,
|
val oldestAllowedDate = LocalDate.now().minusWeeks(13)
|
||||||
items: List<StreamInfoItem>,
|
val itemsToInsert = items.filter {
|
||||||
oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE
|
val uploadDate = it.uploadDate?.localDateTime?.toLocalDate()
|
||||||
) {
|
|
||||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
|
||||||
loop@ for (streamItem in items) {
|
|
||||||
val uploadDate = streamItem.uploadDate
|
|
||||||
|
|
||||||
itemsToInsert += when {
|
(uploadDate == null && it.streamType == StreamType.LIVE_STREAM) ||
|
||||||
uploadDate == null && streamItem.streamType == StreamType.LIVE_STREAM -> streamItem
|
(uploadDate != null && uploadDate >= oldestAllowedDate)
|
||||||
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate -> streamItem
|
|
||||||
else -> continue@loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
feedTable.unlinkOldLivestreams(subscriptionId)
|
feedTable.unlinkOldLivestreams(subscriptionId)
|
||||||
|
@ -107,12 +92,14 @@ class FeedDatabaseManager(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
feedTable.setLastUpdatedForSubscription(
|
feedTable.setLastUpdatedForSubscription(
|
||||||
FeedLastUpdatedEntity(subscriptionId, OffsetDateTime.now(ZoneOffset.UTC))
|
FeedLastUpdatedEntity(subscriptionId, Instant.now())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
|
fun removeOrphansOrOlderStreams() {
|
||||||
feedTable.unlinkStreamsOlderThan(oldestAllowedDate)
|
val oldestAllowedDate = LocalDate.now().minusWeeks(13)
|
||||||
|
val instant = oldestAllowedDate.atStartOfDay(ZoneId.systemDefault()).toInstant()
|
||||||
|
feedTable.unlinkStreamsOlderThan(instant)
|
||||||
streamTable.deleteOrphans()
|
streamTable.deleteOrphans()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,10 +164,10 @@ class FeedDatabaseManager(context: Context) {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> {
|
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<Instant>> {
|
||||||
return when (groupId) {
|
return when (groupId) {
|
||||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
FeedGroupEntity.GROUP_ALL_ID -> feedTable.getOldestSubscriptionUpdateFromAll()
|
||||||
else -> feedTable.oldestSubscriptionUpdate(groupId)
|
else -> feedTable.getOldestSubscriptionUpdate(groupId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
|
||||||
import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
|
import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
|
||||||
import org.schabi.newpipe.util.ThemeHelper.resolveDrawable
|
import org.schabi.newpipe.util.ThemeHelper.resolveDrawable
|
||||||
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
|
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
|
||||||
class FeedFragment : BaseStateFragment<FeedState>() {
|
class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
|
@ -95,7 +95,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
|
|
||||||
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
||||||
private var groupName = ""
|
private var groupName = ""
|
||||||
private var oldestSubscriptionUpdate: OffsetDateTime? = null
|
private var oldestSubscriptionUpdate: Instant? = null
|
||||||
|
|
||||||
private lateinit var groupAdapter: GroupieAdapter
|
private lateinit var groupAdapter: GroupieAdapter
|
||||||
|
|
||||||
|
@ -415,8 +415,8 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
val oldOldestSubscriptionUpdate = oldestSubscriptionUpdate
|
val oldOldestSubscriptionUpdate = oldestSubscriptionUpdate
|
||||||
|
|
||||||
groupAdapter.updateAsync(loadedState.items, false) {
|
groupAdapter.updateAsync(loadedState.items, false) {
|
||||||
oldOldestSubscriptionUpdate?.run {
|
oldOldestSubscriptionUpdate?.let {
|
||||||
highlightNewItemsAfter(oldOldestSubscriptionUpdate)
|
highlightNewItemsAfter(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,14 +543,14 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
private fun updateRefreshViewState() {
|
private fun updateRefreshViewState() {
|
||||||
feedBinding.refreshText.text = getString(
|
feedBinding.refreshText.text = getString(
|
||||||
R.string.feed_oldest_subscription_update,
|
R.string.feed_oldest_subscription_update,
|
||||||
oldestSubscriptionUpdate?.let { Localization.relativeTime(it) } ?: "—"
|
oldestSubscriptionUpdate?.let { Localization.formatRelativeTime(it) } ?: "—"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlights all items that are after the specified time
|
* Highlights all items that are after the specified time
|
||||||
*/
|
*/
|
||||||
private fun highlightNewItemsAfter(updateTime: OffsetDateTime) {
|
private fun highlightNewItemsAfter(updateTime: Instant) {
|
||||||
var highlightCount = 0
|
var highlightCount = 0
|
||||||
|
|
||||||
var doCheck = true
|
var doCheck = true
|
||||||
|
@ -563,8 +563,9 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||||
resolveDrawable(ctx, android.R.attr.selectableItemBackground)
|
resolveDrawable(ctx, android.R.attr.selectableItemBackground)
|
||||||
}
|
}
|
||||||
if (doCheck) {
|
if (doCheck) {
|
||||||
|
val instant = item.streamWithState.stream.uploadInstant
|
||||||
// If the uploadDate is null or true we should highlight the item
|
// If the uploadDate is null or true we should highlight the item
|
||||||
if (item.streamWithState.stream.uploadDate?.isAfter(updateTime) != false) {
|
if (instant != null && instant > updateTime) {
|
||||||
highlightCount++
|
highlightCount++
|
||||||
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.schabi.newpipe.local.feed
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import org.schabi.newpipe.local.feed.item.StreamItem
|
import org.schabi.newpipe.local.feed.item.StreamItem
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
sealed class FeedState {
|
sealed class FeedState {
|
||||||
data class ProgressState(
|
data class ProgressState(
|
||||||
|
@ -13,7 +13,7 @@ sealed class FeedState {
|
||||||
|
|
||||||
data class LoadedState(
|
data class LoadedState(
|
||||||
val items: List<StreamItem>,
|
val items: List<StreamItem>,
|
||||||
val oldestUpdate: OffsetDateTime?,
|
val oldestUpdate: Instant?,
|
||||||
val notLoadedCount: Long,
|
val notLoadedCount: Long,
|
||||||
val itemsErrors: List<Throwable>
|
val itemsErrors: List<Throwable>
|
||||||
) : FeedState()
|
) : FeedState()
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class FeedViewModel(
|
class FeedViewModel(
|
||||||
|
@ -61,11 +61,11 @@ class FeedViewModel(
|
||||||
showPlayedItemsFlowable,
|
showPlayedItemsFlowable,
|
||||||
showPartiallyPlayedItemsFlowable,
|
showPartiallyPlayedItemsFlowable,
|
||||||
showFutureItemsFlowable,
|
showFutureItemsFlowable,
|
||||||
feedDatabaseManager.notLoadedCount(groupId),
|
feedDatabaseManager.getNotLoadedCount(groupId),
|
||||||
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
|
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
|
||||||
|
|
||||||
Function6 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, t4: Boolean,
|
Function6 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, t4: Boolean,
|
||||||
t5: Long, t6: List<OffsetDateTime> ->
|
t5: Long, t6: List<Instant> ->
|
||||||
return@Function6 CombineResultEventHolder(t1, t2, t3, t4, t5, t6.firstOrNull())
|
return@Function6 CombineResultEventHolder(t1, t2, t3, t4, t5, t6.firstOrNull())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -109,14 +109,14 @@ class FeedViewModel(
|
||||||
val t3: Boolean,
|
val t3: Boolean,
|
||||||
val t4: Boolean,
|
val t4: Boolean,
|
||||||
val t5: Long,
|
val t5: Long,
|
||||||
val t6: OffsetDateTime?
|
val t6: Instant?
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class CombineResultDataHolder(
|
private data class CombineResultDataHolder(
|
||||||
val t1: FeedEventManager.Event,
|
val t1: FeedEventManager.Event,
|
||||||
val t2: List<StreamWithState>,
|
val t2: List<StreamWithState>,
|
||||||
val t3: Long,
|
val t3: Long,
|
||||||
val t4: OffsetDateTime?
|
val t4: Instant?
|
||||||
)
|
)
|
||||||
|
|
||||||
fun setSaveShowPlayedItems(showPlayedItems: Boolean) {
|
fun setSaveShowPlayedItems(showPlayedItems: Boolean) {
|
||||||
|
|
|
@ -137,9 +137,9 @@ data class StreamItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormattedRelativeUploadDate(context: Context): String? {
|
private fun getFormattedRelativeUploadDate(context: Context): String? {
|
||||||
val uploadDate = stream.uploadDate
|
val uploadDate = stream.uploadInstant
|
||||||
return if (uploadDate != null) {
|
return if (uploadDate != null) {
|
||||||
var formattedRelativeTime = Localization.relativeTime(uploadDate)
|
var formattedRelativeTime = Localization.formatRelativeTime(uploadDate)
|
||||||
|
|
||||||
if (MainActivity.DEBUG) {
|
if (MainActivity.DEBUG) {
|
||||||
val key = context.getString(R.string.show_original_time_ago_key)
|
val key = context.getString(R.string.show_original_time_ago_key)
|
||||||
|
|
|
@ -27,8 +27,8 @@ import org.schabi.newpipe.util.ChannelTabHelper
|
||||||
import org.schabi.newpipe.util.ExtractorHelper.getChannelInfo
|
import org.schabi.newpipe.util.ExtractorHelper.getChannelInfo
|
||||||
import org.schabi.newpipe.util.ExtractorHelper.getChannelTab
|
import org.schabi.newpipe.util.ExtractorHelper.getChannelTab
|
||||||
import org.schabi.newpipe.util.ExtractorHelper.getMoreChannelTabItems
|
import org.schabi.newpipe.util.ExtractorHelper.getMoreChannelTabItems
|
||||||
import java.time.OffsetDateTime
|
import java.time.Instant
|
||||||
import java.time.ZoneOffset
|
import java.time.LocalDate
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
@ -69,26 +69,26 @@ class FeedLoadManager(private val context: Context) {
|
||||||
)
|
)
|
||||||
|
|
||||||
val outdatedThreshold = if (ignoreOutdatedThreshold) {
|
val outdatedThreshold = if (ignoreOutdatedThreshold) {
|
||||||
OffsetDateTime.now(ZoneOffset.UTC)
|
Instant.now()
|
||||||
} else {
|
} else {
|
||||||
val thresholdOutdatedSeconds = defaultSharedPreferences.getStringSafe(
|
val thresholdOutdatedSeconds = defaultSharedPreferences.getStringSafe(
|
||||||
context.getString(R.string.feed_update_threshold_key),
|
context.getString(R.string.feed_update_threshold_key),
|
||||||
context.getString(R.string.feed_update_threshold_default_value)
|
context.getString(R.string.feed_update_threshold_default_value)
|
||||||
).toInt()
|
).toLong()
|
||||||
OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong())
|
Instant.now().minusSeconds(thresholdOutdatedSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* subscriptions which have not been updated within the feed updated threshold
|
* subscriptions which have not been updated within the feed updated threshold
|
||||||
*/
|
*/
|
||||||
val outdatedSubscriptions = when (groupId) {
|
val outdatedSubscriptions = when (groupId) {
|
||||||
FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(
|
FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.getOutdatedSubscriptions(
|
||||||
outdatedThreshold
|
outdatedThreshold
|
||||||
)
|
)
|
||||||
GROUP_NOTIFICATION_ENABLED -> feedDatabaseManager.outdatedSubscriptionsWithNotificationMode(
|
GROUP_NOTIFICATION_ENABLED -> feedDatabaseManager.getOutdatedSubscriptionsWithNotificationMode(
|
||||||
outdatedThreshold, NotificationMode.ENABLED
|
outdatedThreshold, NotificationMode.ENABLED
|
||||||
)
|
)
|
||||||
else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold)
|
else -> feedDatabaseManager.getOutdatedSubscriptionsForGroup(groupId, outdatedThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
// like `currentProgress`, but counts the number of YouTube extractions that have begun, so
|
// like `currentProgress`, but counts the number of YouTube extractions that have begun, so
|
||||||
|
@ -253,7 +253,7 @@ class FeedLoadManager(private val context: Context) {
|
||||||
* Keep the feed and the stream tables small
|
* Keep the feed and the stream tables small
|
||||||
* to reduce loading times when trying to display the feed.
|
* to reduce loading times when trying to display the feed.
|
||||||
* <br>
|
* <br>
|
||||||
* Remove streams from the feed which are older than [FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE].
|
* Remove streams from the feed which are older than 13 weeks.
|
||||||
* Remove streams from the database which are not linked / used by any table.
|
* Remove streams from the database which are not linked / used by any table.
|
||||||
*/
|
*/
|
||||||
private fun postProcessFeed() = Completable.fromRunnable {
|
private fun postProcessFeed() = Completable.fromRunnable {
|
||||||
|
@ -319,15 +319,13 @@ class FeedLoadManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterNewStreams(list: List<StreamInfoItem>): List<StreamInfoItem> {
|
private fun filterNewStreams(list: List<StreamInfoItem>): List<StreamInfoItem> {
|
||||||
|
val oldestAllowedDate = LocalDate.now().minusWeeks(13)
|
||||||
return list.filter {
|
return list.filter {
|
||||||
!feedDatabaseManager.doesStreamExist(it) &&
|
|
||||||
it.uploadDate != null &&
|
|
||||||
// Streams older than this date are automatically removed from the feed.
|
// Streams older than this date are automatically removed from the feed.
|
||||||
// Therefore, streams which are not in the database,
|
// Therefore, streams which are not in the database,
|
||||||
// but older than this date, are considered old.
|
// but older than this date, are considered old.
|
||||||
it.uploadDate!!.offsetDateTime().isAfter(
|
val date = it.uploadDate?.localDateTime?.toLocalDate()
|
||||||
FeedDatabaseManager.FEED_OLDEST_ALLOWED_DATE
|
!feedDatabaseManager.doesStreamExist(it) && date != null && date > oldestAllowedDate
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,7 @@ import org.schabi.newpipe.local.feed.FeedViewModel;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.Instant;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ public class HistoryRecordManager {
|
||||||
return Maybe.empty();
|
return Maybe.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
|
final var currentTime = Instant.now();
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
final long streamId;
|
final long streamId;
|
||||||
final long duration;
|
final long duration;
|
||||||
|
@ -139,14 +138,14 @@ public class HistoryRecordManager {
|
||||||
return Maybe.empty();
|
return Maybe.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
|
final var currentTime = Instant.now();
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||||
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||||
|
|
||||||
if (latestEntry != null) {
|
if (latestEntry != null) {
|
||||||
streamHistoryTable.delete(latestEntry);
|
streamHistoryTable.delete(latestEntry);
|
||||||
latestEntry.setAccessDate(currentTime);
|
latestEntry.setAccessInstant(currentTime);
|
||||||
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
|
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
|
||||||
return streamHistoryTable.insert(latestEntry);
|
return streamHistoryTable.insert(latestEntry);
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,13 +193,13 @@ public class HistoryRecordManager {
|
||||||
return Maybe.empty();
|
return Maybe.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
|
final var currentTime = Instant.now();
|
||||||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||||
|
|
||||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||||
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||||
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
||||||
latestEntry.setCreationDate(currentTime);
|
latestEntry.setCreationInstant(currentTime);
|
||||||
return (long) searchHistoryTable.update(latestEntry);
|
return (long) searchHistoryTable.update(latestEntry);
|
||||||
} else {
|
} else {
|
||||||
return searchHistoryTable.insert(newEntry);
|
return searchHistoryTable.insert(newEntry);
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class StatisticsPlaylistFragment
|
||||||
final Comparator<StreamStatisticsEntry> comparator;
|
final Comparator<StreamStatisticsEntry> comparator;
|
||||||
switch (sortMode) {
|
switch (sortMode) {
|
||||||
case LAST_PLAYED:
|
case LAST_PLAYED:
|
||||||
comparator = Comparator.comparing(StreamStatisticsEntry::getLatestAccessDate);
|
comparator = Comparator.comparing(StreamStatisticsEntry::getLatestAccessInstant);
|
||||||
break;
|
break;
|
||||||
case MOST_PLAYED:
|
case MOST_PLAYED:
|
||||||
comparator = Comparator.comparingLong(StreamStatisticsEntry::getWatchCount);
|
comparator = Comparator.comparingLong(StreamStatisticsEntry::getWatchCount);
|
||||||
|
|
|
@ -10,8 +10,6 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
public class LocalBookmarkPlaylistItemHolder extends LocalPlaylistItemHolder {
|
public class LocalBookmarkPlaylistItemHolder extends LocalPlaylistItemHolder {
|
||||||
private final View itemHandleView;
|
private final View itemHandleView;
|
||||||
|
|
||||||
|
@ -28,16 +26,14 @@ public class LocalBookmarkPlaylistItemHolder extends LocalPlaylistItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof PlaylistMetadataEntry item)) {
|
||||||
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
|
||||||
|
|
||||||
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
||||||
|
|
||||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
super.updateFromItem(localItem, historyRecordManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private View.OnTouchListener getOnTouchListener(final PlaylistMetadataEntry item) {
|
private View.OnTouchListener getOnTouchListener(final PlaylistMetadataEntry item) {
|
||||||
|
|
|
@ -9,8 +9,6 @@ import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 12.02.17.
|
* Created by Christian Schabesberger on 12.02.17.
|
||||||
*
|
*
|
||||||
|
@ -40,8 +38,7 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
|
||||||
this.itemBuilder = itemBuilder;
|
this.itemBuilder = itemBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager,
|
public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager);
|
||||||
DateTimeFormatter dateTimeFormatter);
|
|
||||||
|
|
||||||
public void updateState(final LocalItem localItem,
|
public void updateState(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager) { }
|
final HistoryRecordManager historyRecordManager) { }
|
||||||
|
|
|
@ -8,10 +8,8 @@ import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
|
||||||
|
@ -28,12 +26,10 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof PlaylistMetadataEntry item)) {
|
||||||
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
|
||||||
|
|
||||||
itemTitleView.setText(item.name);
|
itemTitleView.setText(item.name);
|
||||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||||
|
@ -49,6 +45,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||||
itemView.setAlpha(1.0f);
|
itemView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
super.updateFromItem(localItem, historyRecordManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,10 @@ import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
|
@ -50,12 +49,10 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof PlaylistStreamEntry item)) {
|
||||||
if (!(localItem instanceof PlaylistStreamEntry)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
|
||||||
|
|
||||||
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
||||||
itemAdditionalDetailsView.setText(Localization
|
itemAdditionalDetailsView.setText(Localization
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package org.schabi.newpipe.local.holder;
|
package org.schabi.newpipe.local.holder;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.ServiceHelper.getNameOfServiceById;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.i18n.MessageFormat;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.LocalItem;
|
import org.schabi.newpipe.database.LocalItem;
|
||||||
|
@ -17,10 +21,11 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.ZoneId;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -69,24 +74,24 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
|
@NonNull
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
private String getStreamInfoDetailLine(@NonNull final StreamStatisticsEntry entry) {
|
||||||
return Localization.concatenateStrings(
|
final var context = itemBuilder.getContext();
|
||||||
// watchCount
|
final var zdt = entry.getLatestAccessInstant().atZone(ZoneId.systemDefault());
|
||||||
Localization.shortViewCount(itemBuilder.getContext(), entry.getWatchCount()),
|
final Map<String, Object> args = Map.of(
|
||||||
dateTimeFormatter.format(entry.getLatestAccessDate()),
|
"formatted_views", Localization.shortViewCount(context, entry.getWatchCount()),
|
||||||
// serviceName
|
"last_viewed_date", GregorianCalendar.from(zdt),
|
||||||
ServiceHelper.getNameOfServiceById(entry.getStreamEntity().getServiceId()));
|
"service_name", getNameOfServiceById(entry.getStreamEntity().getServiceId())
|
||||||
|
);
|
||||||
|
return MessageFormat.format(context, R.string.history_detail_line, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof StreamStatisticsEntry item)) {
|
||||||
if (!(localItem instanceof StreamStatisticsEntry)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
|
||||||
|
|
||||||
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
itemVideoTitleView.setText(item.getStreamEntity().getTitle());
|
||||||
itemUploaderView.setText(item.getStreamEntity().getUploader());
|
itemUploaderView.setText(item.getStreamEntity().getUploader());
|
||||||
|
@ -113,7 +118,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemAdditionalDetails != null) {
|
if (itemAdditionalDetails != null) {
|
||||||
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateTimeFormatter));
|
itemAdditionalDetails.setText(getStreamInfoDetailLine(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||||
|
|
|
@ -9,8 +9,6 @@ import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
public abstract class PlaylistItemHolder extends LocalItemHolder {
|
public abstract class PlaylistItemHolder extends LocalItemHolder {
|
||||||
public final ImageView itemThumbnailView;
|
public final ImageView itemThumbnailView;
|
||||||
final TextView itemStreamCountView;
|
final TextView itemStreamCountView;
|
||||||
|
@ -33,8 +31,7 @@ public abstract class PlaylistItemHolder extends LocalItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||||
itemBuilder.getOnItemSelectedListener().selected(localItem);
|
itemBuilder.getOnItemSelectedListener().selected(localItem);
|
||||||
|
|
|
@ -10,8 +10,6 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
public class RemoteBookmarkPlaylistItemHolder extends RemotePlaylistItemHolder {
|
public class RemoteBookmarkPlaylistItemHolder extends RemotePlaylistItemHolder {
|
||||||
private final View itemHandleView;
|
private final View itemHandleView;
|
||||||
|
|
||||||
|
@ -28,16 +26,14 @@ public class RemoteBookmarkPlaylistItemHolder extends RemotePlaylistItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof PlaylistRemoteEntity item)) {
|
||||||
if (!(localItem instanceof PlaylistRemoteEntity)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
|
||||||
|
|
||||||
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
||||||
|
|
||||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
super.updateFromItem(localItem, historyRecordManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private View.OnTouchListener getOnTouchListener(final PlaylistRemoteEntity item) {
|
private View.OnTouchListener getOnTouchListener(final PlaylistRemoteEntity item) {
|
||||||
|
|
|
@ -8,10 +8,8 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
|
||||||
|
@ -27,12 +25,10 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFromItem(final LocalItem localItem,
|
public void updateFromItem(final LocalItem localItem,
|
||||||
final HistoryRecordManager historyRecordManager,
|
final HistoryRecordManager historyRecordManager) {
|
||||||
final DateTimeFormatter dateTimeFormatter) {
|
if (!(localItem instanceof PlaylistRemoteEntity item)) {
|
||||||
if (!(localItem instanceof PlaylistRemoteEntity)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
|
||||||
|
|
||||||
itemTitleView.setText(item.getName());
|
itemTitleView.setText(item.getName());
|
||||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||||
|
@ -47,6 +43,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||||
|
|
||||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
||||||
|
|
||||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
super.updateFromItem(localItem, historyRecordManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.util;
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
@ -34,8 +33,7 @@ import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -43,7 +41,6 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by chschtsch on 12/29/15.
|
* Created by chschtsch on 12/29/15.
|
||||||
*
|
*
|
||||||
|
@ -130,16 +127,10 @@ public final class Localization {
|
||||||
return NumberFormat.getInstance(getAppLocale()).format(number);
|
return NumberFormat.getInstance(getAppLocale()).format(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatDate(@NonNull final OffsetDateTime offsetDateTime) {
|
@NonNull
|
||||||
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
|
public static String formatDate(@NonNull final DateWrapper dateWrapper) {
|
||||||
.withLocale(getAppLocale())
|
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(getAppLocale())
|
||||||
.format(offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()));
|
.format(dateWrapper.getLocalDateTime());
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
|
||||||
public static String localizeUploadDate(@NonNull final Context context,
|
|
||||||
@NonNull final OffsetDateTime offsetDateTime) {
|
|
||||||
return context.getString(R.string.upload_date_text, formatDate(offsetDateTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
|
public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
|
||||||
|
@ -379,8 +370,8 @@ public final class Localization {
|
||||||
return new PrettyTime(getAppLocale());
|
return new PrettyTime(getAppLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) {
|
public static String formatRelativeTime(@NonNull final Instant instant) {
|
||||||
return prettyTime.formatUnrounded(offsetDateTime);
|
return prettyTime.formatUnrounded(instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -389,13 +380,13 @@ public final class Localization {
|
||||||
* @param parsed the textual date or time ago parsed by NewPipeExtractor, or {@code null} if
|
* @param parsed the textual date or time ago parsed by NewPipeExtractor, or {@code null} if
|
||||||
* the extractor could not parse it
|
* the extractor could not parse it
|
||||||
* @param textual the original textual date or time ago string as provided by services
|
* @param textual the original textual date or time ago string as provided by services
|
||||||
* @return {@link #relativeTime(OffsetDateTime)} is used if {@code parsed != null}, otherwise
|
* @return {@link #formatRelativeTime(Instant)} is used if {@code parsed != null}, otherwise
|
||||||
* {@code textual} is returned. If in debug mode, {@code context != null},
|
* {@code textual} is returned. If in debug mode, {@code context != null},
|
||||||
* {@code parsed != null} and the relevant setting is enabled, {@code textual} will
|
* {@code parsed != null} and the relevant setting is enabled, {@code textual} will
|
||||||
* be appended to the returned string for debugging purposes.
|
* be appended to the returned string for debugging purposes.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String relativeTimeOrTextual(@Nullable final Context context,
|
public static String formatRelativeTimeOrTextual(@Nullable final Context context,
|
||||||
@Nullable final DateWrapper parsed,
|
@Nullable final DateWrapper parsed,
|
||||||
@Nullable final String textual) {
|
@Nullable final String textual) {
|
||||||
if (parsed == null) {
|
if (parsed == null) {
|
||||||
|
@ -403,9 +394,9 @@ public final class Localization {
|
||||||
} else if (DEBUG && context != null && PreferenceManager
|
} else if (DEBUG && context != null && PreferenceManager
|
||||||
.getDefaultSharedPreferences(context)
|
.getDefaultSharedPreferences(context)
|
||||||
.getBoolean(context.getString(R.string.show_original_time_ago_key), false)) {
|
.getBoolean(context.getString(R.string.show_original_time_ago_key), false)) {
|
||||||
return relativeTime(parsed.offsetDateTime()) + " (" + textual + ")";
|
return formatRelativeTime(parsed.getInstant()) + " (" + textual + ")";
|
||||||
} else {
|
} else {
|
||||||
return relativeTime(parsed.offsetDateTime());
|
return formatRelativeTime(parsed.getInstant());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -882,4 +882,7 @@
|
||||||
<string name="youtube_player_http_403">HTTP error 403 received from server while playing, likely caused by an IP ban or streaming URL deobfuscation issues</string>
|
<string name="youtube_player_http_403">HTTP error 403 received from server while playing, likely caused by an IP ban or streaming URL deobfuscation issues</string>
|
||||||
<string name="sign_in_confirm_not_bot_error">%1$s refused to provide data, asking for a login to confirm the requester is not a bot.\n\nYour IP might have been temporarily banned by %1$s, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data).</string>
|
<string name="sign_in_confirm_not_bot_error">%1$s refused to provide data, asking for a login to confirm the requester is not a bot.\n\nYour IP might have been temporarily banned by %1$s, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data).</string>
|
||||||
<string name="unsupported_content_in_country">This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\".</string>
|
<string name="unsupported_content_in_country">This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\".</string>
|
||||||
|
<string name="history_detail_line">
|
||||||
|
{formatted_views} • {last_viewed_date, date, short} • {service_name}
|
||||||
|
</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -3,25 +3,29 @@ package org.schabi.newpipe.util
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.ocpsoft.prettytime.PrettyTime
|
import org.ocpsoft.prettytime.PrettyTime
|
||||||
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.OffsetDateTime
|
import java.time.Month
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneId
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class LocalizationTest {
|
class LocalizationTest {
|
||||||
@Test(expected = NullPointerException::class)
|
@Test(expected = NullPointerException::class)
|
||||||
fun `relativeTime() must fail without initializing pretty time`() {
|
fun `formatRelativeTime() must fail without initializing pretty time`() {
|
||||||
Localization.relativeTime(OffsetDateTime.of(2021, 1, 6, 0, 0, 0, 0, ZoneOffset.UTC))
|
val instant = Instant.now()
|
||||||
|
Localization.formatRelativeTime(instant)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `relativeTime() with a OffsetDateTime must work`() {
|
fun `formatRelativeTime() with an Instant must work`() {
|
||||||
val prettyTime = PrettyTime(LocalDate.of(2021, 1, 1), ZoneOffset.UTC)
|
val zoneId = ZoneId.systemDefault()
|
||||||
|
val date = LocalDate.of(2021, Month.JANUARY, 1)
|
||||||
|
val prettyTime = PrettyTime(date, zoneId)
|
||||||
prettyTime.locale = Locale.ENGLISH
|
prettyTime.locale = Locale.ENGLISH
|
||||||
Localization.initPrettyTime(prettyTime)
|
Localization.initPrettyTime(prettyTime)
|
||||||
|
|
||||||
val offset = OffsetDateTime.of(2021, 1, 6, 0, 0, 0, 0, ZoneOffset.UTC)
|
val instant = date.plusDays(5).atStartOfDay(zoneId).toInstant()
|
||||||
val actual = Localization.relativeTime(offset)
|
val actual = Localization.formatRelativeTime(instant)
|
||||||
|
|
||||||
assertEquals("5 days from now", actual)
|
assertEquals("5 days from now", actual)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue