diff --git a/.editorconfig b/.editorconfig index 77feb3181..b34abfa11 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,39 +6,13 @@ root = true [*.{kt,kts}] -ktlint_standard_annotation = disabled -ktlint_standard_argument-list-wrapping = disabled -ktlint_standard_backing-property-naming = disabled -ktlint_standard_blank-line-before-declaration = disabled -ktlint_standard_blank-line-between-when-conditions = disabled -ktlint_standard_chain-method-continuation = disabled +ktlint_code_style = android_studio +# https://pinterest.github.io/ktlint/latest/rules/standard/#function-naming +ktlint_function_naming_ignore_when_annotated_with = Composable + ktlint_standard_class-signature = disabled -ktlint_standard_comment-wrapping = disabled -ktlint_standard_enum-wrapping = disabled ktlint_standard_function-expression-body = disabled -ktlint_standard_function-literal = disabled -ktlint_standard_function-signature = disabled -ktlint_standard_indent = disabled -ktlint_standard_kdoc = disabled ktlint_standard_max-line-length = disabled ktlint_standard_mixed-condition-operators = disabled -ktlint_standard_multiline-expression-wrapping = disabled -ktlint_standard_multiline-if-else = disabled -ktlint_standard_no-blank-line-in-list = disabled -ktlint_standard_no-consecutive-comments = disabled -ktlint_standard_no-empty-first-line-in-class-body = disabled -ktlint_standard_no-empty-first-line-in-method-block = disabled -ktlint_standard_no-line-break-after-else = disabled -ktlint_standard_no-semi = disabled -ktlint_standard_no-single-line-block-comment = disabled ktlint_standard_package-name = disabled -ktlint_standard_parameter-list-wrapping = disabled ktlint_standard_property-naming = disabled -ktlint_standard_spacing-between-declarations-with-annotations = disabled -ktlint_standard_spacing-between-declarations-with-comments = disabled -ktlint_standard_statement-wrapping = disabled -ktlint_standard_string-template-indent = disabled -ktlint_standard_trailing-comma-on-call-site = disabled -ktlint_standard_trailing-comma-on-declaration-site = disabled -ktlint_standard_try-catch-finally-spacing = disabled -ktlint_standard_when-entry-bracing = disabled diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt index 4327271f4..fd551b7b8 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/database/DatabaseMigrationTest.kt @@ -176,28 +176,32 @@ class DatabaseMigrationTest { databaseInV7.run { insert( - "search_history", SQLiteDatabase.CONFLICT_FAIL, + "search_history", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", serviceId) put("search", defaultSearch1) } ) insert( - "search_history", SQLiteDatabase.CONFLICT_FAIL, + "search_history", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", serviceId) put("search", defaultSearch2) } ) insert( - "search_history", SQLiteDatabase.CONFLICT_FAIL, + "search_history", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", otherServiceId) put("search", defaultSearch1) } ) insert( - "search_history", SQLiteDatabase.CONFLICT_FAIL, + "search_history", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", otherServiceId) put("search", defaultSearch2) @@ -207,13 +211,17 @@ class DatabaseMigrationTest { } testHelper.runMigrationsAndValidate( - AppDatabase.DATABASE_NAME, Migrations.DB_VER_8, - true, Migrations.MIGRATION_7_8 + AppDatabase.DATABASE_NAME, + Migrations.DB_VER_8, + true, + Migrations.MIGRATION_7_8 ) testHelper.runMigrationsAndValidate( - AppDatabase.DATABASE_NAME, Migrations.DB_VER_9, - true, Migrations.MIGRATION_8_9 + AppDatabase.DATABASE_NAME, + Migrations.DB_VER_9, + true, + Migrations.MIGRATION_8_9 ) val migratedDatabaseV8 = getMigratedDatabase() @@ -235,7 +243,8 @@ class DatabaseMigrationTest { val remoteUid2: Long databaseInV8.run { localUid1 = insert( - "playlists", SQLiteDatabase.CONFLICT_FAIL, + "playlists", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("name", DEFAULT_NAME + "1") put("is_thumbnail_permanent", false) @@ -243,7 +252,8 @@ class DatabaseMigrationTest { } ) localUid2 = insert( - "playlists", SQLiteDatabase.CONFLICT_FAIL, + "playlists", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("name", DEFAULT_NAME + "2") put("is_thumbnail_permanent", false) @@ -251,25 +261,29 @@ class DatabaseMigrationTest { } ) delete( - "playlists", "uid = ?", + "playlists", + "uid = ?", Array(1) { localUid1 } ) remoteUid1 = insert( - "remote_playlists", SQLiteDatabase.CONFLICT_FAIL, + "remote_playlists", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", DEFAULT_SERVICE_ID) put("url", DEFAULT_URL) } ) remoteUid2 = insert( - "remote_playlists", SQLiteDatabase.CONFLICT_FAIL, + "remote_playlists", + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("service_id", DEFAULT_SECOND_SERVICE_ID) put("url", DEFAULT_SECOND_URL) } ) delete( - "remote_playlists", "uid = ?", + "remote_playlists", + "uid = ?", Array(1) { remoteUid2 } ) close() diff --git a/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt b/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt index 54d2dad9c..a61caad06 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt @@ -4,6 +4,8 @@ import android.content.Context import androidx.room.Room import androidx.test.core.app.ApplicationProvider import io.reactivex.rxjava3.core.Single +import java.io.IOException +import java.time.OffsetDateTime import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -20,8 +22,6 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.extractor.ServiceList import org.schabi.newpipe.extractor.channel.ChannelInfo import org.schabi.newpipe.extractor.stream.StreamType -import java.io.IOException -import java.time.OffsetDateTime class FeedDAOTest { private lateinit var db: AppDatabase @@ -40,14 +40,21 @@ class FeedDAOTest { private val stream7 = StreamEntity(7, serviceId, "https://youtube.com/watch?v=7", "stream 7", StreamType.VIDEO_STREAM, 1000, "channel-4", "https://youtube.com/channel/4", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z")) private val allStreams = listOf( - stream1, stream2, stream3, stream4, stream5, stream6, stream7 + stream1, + stream2, + stream3, + stream4, + stream5, + stream6, + stream7 ) @Before fun createDb() { val context = ApplicationProvider.getApplicationContext() db = Room.inMemoryDatabaseBuilder( - context, AppDatabase::class.java + context, + AppDatabase::class.java ).build() feedDAO = db.feedDAO() streamDAO = db.streamDAO() @@ -64,7 +71,10 @@ class FeedDAOTest { fun testUnlinkStreamsOlderThan_KeepOne() { setupUnlinkDelete("2023-08-15T00:00:00Z") val streams = feedDAO.getStreams( - FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null + FeedGroupEntity.GROUP_ALL_ID, + includePlayed = true, + includePartiallyPlayed = true, + null ) .blockingGet() val allowedStreams = listOf(stream3, stream5, stream6, stream7) @@ -75,7 +85,10 @@ class FeedDAOTest { fun testUnlinkStreamsOlderThan_KeepMultiple() { setupUnlinkDelete("2023-08-01T00:00:00Z") val streams = feedDAO.getStreams( - FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null + FeedGroupEntity.GROUP_ALL_ID, + includePlayed = true, + includePartiallyPlayed = true, + null ) .blockingGet() val allowedStreams = listOf(stream3, stream4, stream5, stream6, stream7) @@ -111,7 +124,7 @@ class FeedDAOTest { SubscriptionEntity.from(ChannelInfo(serviceId, "1", "https://youtube.com/channel/1", "https://youtube.com/channel/1", "channel-1")), SubscriptionEntity.from(ChannelInfo(serviceId, "2", "https://youtube.com/channel/2", "https://youtube.com/channel/2", "channel-2")), SubscriptionEntity.from(ChannelInfo(serviceId, "3", "https://youtube.com/channel/3", "https://youtube.com/channel/3", "channel-3")), - SubscriptionEntity.from(ChannelInfo(serviceId, "4", "https://youtube.com/channel/4", "https://youtube.com/channel/4", "channel-4")), + SubscriptionEntity.from(ChannelInfo(serviceId, "4", "https://youtube.com/channel/4", "https://youtube.com/channel/4", "channel-4")) ) ) feedDAO.insertAll( @@ -122,7 +135,7 @@ class FeedDAOTest { FeedEntity(4, 2), FeedEntity(5, 2), FeedEntity(6, 3), - FeedEntity(7, 4), + FeedEntity(7, 4) ) ) } diff --git a/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt index 0de9dd268..32fb08db0 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt @@ -1,6 +1,9 @@ package org.schabi.newpipe.local.history import androidx.test.core.app.ApplicationProvider +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Assert.assertEquals @@ -11,9 +14,6 @@ import org.schabi.newpipe.database.AppDatabase import org.schabi.newpipe.database.history.model.SearchHistoryEntry import org.schabi.newpipe.testUtil.TestDatabase import org.schabi.newpipe.testUtil.TrampolineSchedulerRule -import java.time.LocalDateTime -import java.time.OffsetDateTime -import java.time.ZoneOffset class HistoryRecordManagerTest { @@ -54,7 +54,7 @@ class HistoryRecordManagerTest { SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 0, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 1, search = "B"), - SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 0, search = "B"), + SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 0, search = "B") ) // make sure all 4 were inserted @@ -85,7 +85,7 @@ class HistoryRecordManagerTest { val entries = listOf( SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "B"), - SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 0, search = "C"), + SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 0, search = "C") ) // make sure all 3 were inserted @@ -98,7 +98,6 @@ class HistoryRecordManagerTest { } private fun insertShuffledRelatedSearches(relatedSearches: Collection) { - // shuffle to make sure the order of items returned by queries depends only on // SearchHistoryEntry.creationDate, not on the actual insertion time, so that we can // verify that the `ORDER BY` clause does its job @@ -121,7 +120,7 @@ class HistoryRecordManagerTest { RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places) RELATED_SEARCHES_ENTRIES[4].search, // B RELATED_SEARCHES_ENTRIES[5].search, // AA - RELATED_SEARCHES_ENTRIES[2].search, // BA + RELATED_SEARCHES_ENTRIES[2].search // BA ) } @@ -136,7 +135,7 @@ class HistoryRecordManagerTest { SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 3, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "A"), - SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 2, search = "AA"), + SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 2, search = "AA") ) insertShuffledRelatedSearches(relatedSearches) @@ -153,7 +152,7 @@ class HistoryRecordManagerTest { assertThat(searches).containsExactly( RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places) RELATED_SEARCHES_ENTRIES[5].search, // AA - RELATED_SEARCHES_ENTRIES[1].search, // BA + RELATED_SEARCHES_ENTRIES[1].search // BA ) // also make sure that the string comparison is case insensitive @@ -171,7 +170,7 @@ class HistoryRecordManagerTest { SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"), SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "B"), SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 2, search = "AA"), - SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A"), + SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A") ) } } diff --git a/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt b/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt index ce3aeb84a..ac9e84228 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt @@ -33,8 +33,12 @@ class LocalPlaylistManagerTest { fun createPlaylist() { val NEWPIPE_URL = "https://newpipe.net/" val stream = StreamEntity( - serviceId = 1, url = NEWPIPE_URL, title = "title", - streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader", + serviceId = 1, + url = NEWPIPE_URL, + title = "title", + streamType = StreamType.VIDEO_STREAM, + duration = 1, + uploader = "uploader", uploaderUrl = NEWPIPE_URL ) @@ -58,14 +62,22 @@ class LocalPlaylistManagerTest { @Test() fun createPlaylist_nonExistentStreamsAreUpserted() { val stream = StreamEntity( - serviceId = 1, url = "https://newpipe.net/", title = "title", - streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader", + serviceId = 1, + url = "https://newpipe.net/", + title = "title", + streamType = StreamType.VIDEO_STREAM, + duration = 1, + uploader = "uploader", uploaderUrl = "https://newpipe.net/" ) database.streamDAO().insert(stream) val upserted = StreamEntity( - serviceId = 1, url = "https://newpipe.net/2", title = "title2", - streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader", + serviceId = 1, + url = "https://newpipe.net/2", + title = "title2", + streamType = StreamType.VIDEO_STREAM, + duration = 1, + uploader = "uploader", uploaderUrl = "https://newpipe.net/" ) diff --git a/app/src/androidTest/java/org/schabi/newpipe/testUtil/TrampolineSchedulerRule.kt b/app/src/androidTest/java/org/schabi/newpipe/testUtil/TrampolineSchedulerRule.kt index 75f5c6195..8b9f6b752 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/testUtil/TrampolineSchedulerRule.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/testUtil/TrampolineSchedulerRule.kt @@ -17,21 +17,20 @@ class TrampolineSchedulerRule : TestRule { private val scheduler = Schedulers.trampoline() - override fun apply(base: Statement, description: Description): Statement = - object : Statement() { - override fun evaluate() { - try { - RxJavaPlugins.setComputationSchedulerHandler { scheduler } - RxJavaPlugins.setIoSchedulerHandler { scheduler } - RxJavaPlugins.setNewThreadSchedulerHandler { scheduler } - RxJavaPlugins.setSingleSchedulerHandler { scheduler } - RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler } + override fun apply(base: Statement, description: Description): Statement = object : Statement() { + override fun evaluate() { + try { + RxJavaPlugins.setComputationSchedulerHandler { scheduler } + RxJavaPlugins.setIoSchedulerHandler { scheduler } + RxJavaPlugins.setNewThreadSchedulerHandler { scheduler } + RxJavaPlugins.setSingleSchedulerHandler { scheduler } + RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler } - base.evaluate() - } finally { - RxJavaPlugins.reset() - RxAndroidPlugins.reset() - } + base.evaluate() + } finally { + RxJavaPlugins.reset() + RxAndroidPlugins.reset() } } + } } diff --git a/app/src/androidTest/java/org/schabi/newpipe/util/StreamItemAdapterTest.kt b/app/src/androidTest/java/org/schabi/newpipe/util/StreamItemAdapterTest.kt index 9b8ee211e..22c7887f9 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/util/StreamItemAdapterTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/util/StreamItemAdapterTest.kt @@ -156,41 +156,51 @@ class StreamItemAdapterTest { helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0) helper.assertInvalidResponse( - getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), 1 + getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), + 1 ) helper.assertInvalidResponse( - getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), 2 + getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), + 2 ) helper.assertInvalidResponse( - getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), 3 + getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), + 3 ) helper.assertInvalidResponse( - getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"fieldName\"; filename*=\"filename.jpg\""))), 4 + getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"fieldName\"; filename*=\"filename.jpg\""))), + 4 ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.ogg\""))), - 5, MediaFormat.OGG + 5, + MediaFormat.OGG ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "some-form-data; filename=\"audio.flac\""))), - 6, MediaFormat.FLAC + 6, + MediaFormat.FLAC ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.aiff\"; filename=\"audio.aiff\""))), - 7, MediaFormat.AIFF + 7, + MediaFormat.AIFF ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"alien?\"; filename*=UTF-8''%CE%B1%CE%BB%CE%B9%CF%B5%CE%BD.m4a"))), - 8, MediaFormat.M4A + 8, + MediaFormat.M4A ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=UTF-8''alien.opus"))), - 9, MediaFormat.OPUS + 9, + MediaFormat.OPUS ) helper.assertValidResponse( getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=\"UTF-8''alien.opus\""))), - 10, MediaFormat.OPUS + 10, + MediaFormat.OPUS ) } @@ -213,16 +223,24 @@ class StreamItemAdapterTest { helper.assertInvalidResponse(getResponse(mapOf()), 7) helper.assertValidResponse( - getResponse(mapOf(Pair("Content-Type", "audio/flac"))), 8, MediaFormat.FLAC + getResponse(mapOf(Pair("Content-Type", "audio/flac"))), + 8, + MediaFormat.FLAC ) helper.assertValidResponse( - getResponse(mapOf(Pair("Content-Type", "audio/wav"))), 9, MediaFormat.WAV + getResponse(mapOf(Pair("Content-Type", "audio/wav"))), + 9, + MediaFormat.WAV ) helper.assertValidResponse( - getResponse(mapOf(Pair("Content-Type", "audio/opus"))), 10, MediaFormat.OPUS + getResponse(mapOf(Pair("Content-Type", "audio/opus"))), + 10, + MediaFormat.OPUS ) helper.assertValidResponse( - getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), 11, MediaFormat.AIFF + getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), + 11, + MediaFormat.AIFF ) } @@ -230,39 +248,37 @@ class StreamItemAdapterTest { * @return a list of video streams, in which their video only property mirrors the provided * [videoOnly] vararg. */ - private fun getVideoStreams(vararg videoOnly: Boolean) = - StreamItemAdapter.StreamInfoWrapper( - videoOnly.map { - VideoStream.Builder() - .setId(Stream.ID_UNKNOWN) - .setContent("https://example.com", true) - .setMediaFormat(MediaFormat.MPEG_4) - .setResolution("720p") - .setIsVideoOnly(it) - .build() - }, - context - ) + private fun getVideoStreams(vararg videoOnly: Boolean) = StreamInfoWrapper( + videoOnly.map { + VideoStream.Builder() + .setId(Stream.ID_UNKNOWN) + .setContent("https://example.com", true) + .setMediaFormat(MediaFormat.MPEG_4) + .setResolution("720p") + .setIsVideoOnly(it) + .build() + }, + context + ) /** * @return a list of audio streams, containing valid and null elements mirroring the provided * [shouldBeValid] vararg. */ - private fun getAudioStreams(vararg shouldBeValid: Boolean) = - getSecondaryStreamsFromList( - shouldBeValid.map { - if (it) { - AudioStream.Builder() - .setId(Stream.ID_UNKNOWN) - .setContent("https://example.com", true) - .setMediaFormat(MediaFormat.OPUS) - .setAverageBitrate(192) - .build() - } else { - null - } + private fun getAudioStreams(vararg shouldBeValid: Boolean) = getSecondaryStreamsFromList( + shouldBeValid.map { + if (it) { + AudioStream.Builder() + .setId(Stream.ID_UNKNOWN) + .setContent("https://example.com", true) + .setMediaFormat(MediaFormat.OPUS) + .setAverageBitrate(192) + .build() + } else { + null } - ) + } + ) private fun getIncompleteAudioStreams(size: Int): List { val list = ArrayList(size) @@ -292,7 +308,7 @@ class StreamItemAdapterTest { Assert.assertEquals( "normal visibility (pos=[$position]) is not correct", findViewById(R.id.wo_sound_icon).visibility, - normalVisibility, + normalVisibility ) } spinner.adapter.getDropDownView(position, null, spinner).run { @@ -307,18 +323,17 @@ class StreamItemAdapterTest { /** * Helper function that builds a secondary stream list. */ - private fun getSecondaryStreamsFromList(streams: List) = - SparseArrayCompat?>(streams.size).apply { - streams.forEachIndexed { index, stream -> - val secondaryStreamHelper: SecondaryStreamHelper? = stream?.let { - SecondaryStreamHelper( - StreamItemAdapter.StreamInfoWrapper(streams, context), - it - ) - } - put(index, secondaryStreamHelper) + private fun getSecondaryStreamsFromList(streams: List) = SparseArrayCompat?>(streams.size).apply { + streams.forEachIndexed { index, stream -> + val secondaryStreamHelper: SecondaryStreamHelper? = stream?.let { + SecondaryStreamHelper( + StreamItemAdapter.StreamInfoWrapper(streams, context), + it + ) } + put(index, secondaryStreamHelper) } + } private fun getResponse(headers: Map): Response { val listHeaders = HashMap>() @@ -345,7 +360,8 @@ class StreamItemAdapterTest { index: Int ) { assertFalse( - "invalid header returns valid value", retrieveMediaFormat(streams[index], response) + "invalid header returns valid value", + retrieveMediaFormat(streams[index], response) ) assertNull("Media format extracted although stated otherwise", wrapper.getFormat(index)) } @@ -359,7 +375,8 @@ class StreamItemAdapterTest { format: MediaFormat ) { assertTrue( - "header was not recognized", retrieveMediaFormat(streams[index], response) + "header was not recognized", + retrieveMediaFormat(streams[index], response) ) assertEquals("Wrong media format extracted", format, wrapper.getFormat(index)) } diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt index c3ce51524..6527bd2ae 100644 --- a/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt +++ b/app/src/main/java/org/schabi/newpipe/NewPipeDatabase.kt @@ -8,6 +8,7 @@ package org.schabi.newpipe import android.content.Context import androidx.room.Room.databaseBuilder +import kotlin.concurrent.Volatile import org.schabi.newpipe.database.AppDatabase import org.schabi.newpipe.database.Migrations.MIGRATION_1_2 import org.schabi.newpipe.database.Migrations.MIGRATION_2_3 @@ -17,7 +18,6 @@ import org.schabi.newpipe.database.Migrations.MIGRATION_5_6 import org.schabi.newpipe.database.Migrations.MIGRATION_6_7 import org.schabi.newpipe.database.Migrations.MIGRATION_7_8 import org.schabi.newpipe.database.Migrations.MIGRATION_8_9 -import kotlin.concurrent.Volatile object NewPipeDatabase { diff --git a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt index 000b83953..fb48d3f70 100644 --- a/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt @@ -18,10 +18,10 @@ import androidx.work.WorkerParameters import androidx.work.workDataOf import com.grack.nanojson.JsonParser import com.grack.nanojson.JsonParserException +import java.io.IOException import org.schabi.newpipe.extractor.downloader.Response import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.util.ReleaseVersionUtil -import java.io.IOException class NewVersionWorker( context: Context, @@ -46,7 +46,8 @@ class NewVersionWorker( // Show toast stating that the app is up-to-date if the update check was manual. ContextCompat.getMainExecutor(applicationContext).execute { Toast.makeText( - applicationContext, R.string.app_update_unavailable_toast, + applicationContext, + R.string.app_update_unavailable_toast, Toast.LENGTH_SHORT ).show() } @@ -58,7 +59,11 @@ class NewVersionWorker( val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri()) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val pendingIntent = PendingIntentCompat.getActivity( - applicationContext, 0, intent, 0, false + applicationContext, + 0, + intent, + 0, + false ) val channelId = applicationContext.getString(R.string.app_update_notification_channel_id) val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId) @@ -71,7 +76,8 @@ class NewVersionWorker( ) .setContentText( applicationContext.getString( - R.string.app_update_available_notification_text, versionName + R.string.app_update_available_notification_text, + versionName ) ) diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt index 826616e59..cfb504200 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt @@ -116,86 +116,145 @@ class AboutActivity : AppCompatActivity() { */ private val SOFTWARE_COMPONENTS = arrayListOf( SoftwareComponent( - "ACRA", "2013", "Kevin Gaudin", - "https://github.com/ACRA/acra", StandardLicenses.APACHE2 + "ACRA", + "2013", + "Kevin Gaudin", + "https://github.com/ACRA/acra", + StandardLicenses.APACHE2 ), SoftwareComponent( - "AndroidX", "2005 - 2011", "The Android Open Source Project", - "https://developer.android.com/jetpack", StandardLicenses.APACHE2 + "AndroidX", + "2005 - 2011", + "The Android Open Source Project", + "https://developer.android.com/jetpack", + StandardLicenses.APACHE2 ), SoftwareComponent( - "ExoPlayer", "2014 - 2020", "Google, Inc.", - "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2 + "ExoPlayer", + "2014 - 2020", + "Google, Inc.", + "https://github.com/google/ExoPlayer", + StandardLicenses.APACHE2 ), SoftwareComponent( - "GigaGet", "2014 - 2015", "Peter Cai", - "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3 + "GigaGet", + "2014 - 2015", + "Peter Cai", + "https://github.com/PaperAirplane-Dev-Team/GigaGet", + StandardLicenses.GPL3 ), SoftwareComponent( - "Groupie", "2016", "Lisa Wray", - "https://github.com/lisawray/groupie", StandardLicenses.MIT + "Groupie", + "2016", + "Lisa Wray", + "https://github.com/lisawray/groupie", + StandardLicenses.MIT ), SoftwareComponent( - "Android-State", "2018", "Evernote", - "https://github.com/Evernote/android-state", StandardLicenses.EPL1 + "Android-State", + "2018", + "Evernote", + "https://github.com/Evernote/android-state", + StandardLicenses.EPL1 ), SoftwareComponent( - "Bridge", "2021", "Livefront", - "https://github.com/livefront/bridge", StandardLicenses.APACHE2 + "Bridge", + "2021", + "Livefront", + "https://github.com/livefront/bridge", + StandardLicenses.APACHE2 ), SoftwareComponent( - "Jsoup", "2009 - 2020", "Jonathan Hedley", - "https://github.com/jhy/jsoup", StandardLicenses.MIT + "Jsoup", + "2009 - 2020", + "Jonathan Hedley", + "https://github.com/jhy/jsoup", + StandardLicenses.MIT ), SoftwareComponent( - "Markwon", "2019", "Dimitry Ivanov", - "https://github.com/noties/Markwon", StandardLicenses.APACHE2 + "Markwon", + "2019", + "Dimitry Ivanov", + "https://github.com/noties/Markwon", + StandardLicenses.APACHE2 ), SoftwareComponent( - "Material Components for Android", "2016 - 2020", "Google, Inc.", + "Material Components for Android", + "2016 - 2020", + "Google, Inc.", "https://github.com/material-components/material-components-android", StandardLicenses.APACHE2 ), SoftwareComponent( - "NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", - "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3 + "NewPipe Extractor", + "2017 - 2020", + "Christian Schabesberger", + "https://github.com/TeamNewPipe/NewPipeExtractor", + StandardLicenses.GPL3 ), SoftwareComponent( - "NoNonsense-FilePicker", "2016", "Jonas Kalderstam", - "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2 + "NoNonsense-FilePicker", + "2016", + "Jonas Kalderstam", + "https://github.com/spacecowboy/NoNonsense-FilePicker", + StandardLicenses.MPL2 ), SoftwareComponent( - "OkHttp", "2019", "Square, Inc.", - "https://square.github.io/okhttp/", StandardLicenses.APACHE2 + "OkHttp", + "2019", + "Square, Inc.", + "https://square.github.io/okhttp/", + StandardLicenses.APACHE2 ), SoftwareComponent( - "Picasso", "2013", "Square, Inc.", - "https://square.github.io/picasso/", StandardLicenses.APACHE2 + "Picasso", + "2013", + "Square, Inc.", + "https://square.github.io/picasso/", + StandardLicenses.APACHE2 ), SoftwareComponent( - "PrettyTime", "2012 - 2020", "Lincoln Baxter, III", - "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2 + "PrettyTime", + "2012 - 2020", + "Lincoln Baxter, III", + "https://github.com/ocpsoft/prettytime", + StandardLicenses.APACHE2 ), SoftwareComponent( - "ProcessPhoenix", "2015", "Jake Wharton", - "https://github.com/JakeWharton/ProcessPhoenix", StandardLicenses.APACHE2 + "ProcessPhoenix", + "2015", + "Jake Wharton", + "https://github.com/JakeWharton/ProcessPhoenix", + StandardLicenses.APACHE2 ), SoftwareComponent( - "RxAndroid", "2015", "The RxAndroid authors", - "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2 + "RxAndroid", + "2015", + "The RxAndroid authors", + "https://github.com/ReactiveX/RxAndroid", + StandardLicenses.APACHE2 ), SoftwareComponent( - "RxBinding", "2015", "Jake Wharton", - "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2 + "RxBinding", + "2015", + "Jake Wharton", + "https://github.com/JakeWharton/RxBinding", + StandardLicenses.APACHE2 ), SoftwareComponent( - "RxJava", "2016 - 2020", "RxJava Contributors", - "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2 + "RxJava", + "2016 - 2020", + "RxJava Contributors", + "https://github.com/ReactiveX/RxJava", + StandardLicenses.APACHE2 ), SoftwareComponent( - "SearchPreference", "2018", "ByteHamster", - "https://github.com/ByteHamster/SearchPreference", StandardLicenses.MIT - ), + "SearchPreference", + "2018", + "ByteHamster", + "https://github.com/ByteHamster/SearchPreference", + StandardLicenses.MIT + ) ) } } diff --git a/app/src/main/java/org/schabi/newpipe/about/License.kt b/app/src/main/java/org/schabi/newpipe/about/License.kt index 117ff9bf5..fc50c646d 100644 --- a/app/src/main/java/org/schabi/newpipe/about/License.kt +++ b/app/src/main/java/org/schabi/newpipe/about/License.kt @@ -1,8 +1,8 @@ package org.schabi.newpipe.about import android.os.Parcelable -import kotlinx.parcelize.Parcelize import java.io.Serializable +import kotlinx.parcelize.Parcelize /** * Class for storing information about a software license. diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt index 240e2f42b..bd0632c13 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt @@ -97,7 +97,8 @@ class LicenseFragment : Fragment() { .observeOn(AndroidSchedulers.mainThread()) .subscribe { formattedLicense -> val webViewData = Base64.encodeToString( - formattedLicense.toByteArray(), Base64.NO_PADDING + formattedLicense.toByteArray(), + Base64.NO_PADDING ) val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 56e21c88a..32e4f812f 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -1,9 +1,9 @@ package org.schabi.newpipe.about import android.content.Context +import java.io.IOException import org.schabi.newpipe.R import org.schabi.newpipe.util.ThemeHelper -import java.io.IOException /** * @param context the context to use @@ -28,13 +28,16 @@ fun getFormattedLicense(context: Context, license: License): String { fun getLicenseStylesheet(context: Context): String { val isLightTheme = ThemeHelper.isLightThemeSelected(context) val licenseBackgroundColor = getHexRGBColor( - context, if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color + context, + if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color ) val licenseTextColor = getHexRGBColor( - context, if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color + context, + if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color ) val youtubePrimaryColor = getHexRGBColor( - context, if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color + context, + if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color ) return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" + "a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}" diff --git a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt b/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt index 262641caa..a43ddfd5e 100644 --- a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt +++ b/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt @@ -1,8 +1,8 @@ package org.schabi.newpipe.about import android.os.Parcelable -import kotlinx.parcelize.Parcelize import java.io.Serializable +import kotlinx.parcelize.Parcelize @Parcelize class SoftwareComponent diff --git a/app/src/main/java/org/schabi/newpipe/database/Converters.kt b/app/src/main/java/org/schabi/newpipe/database/Converters.kt index ec097cc1b..f9cbb1de2 100644 --- a/app/src/main/java/org/schabi/newpipe/database/Converters.kt +++ b/app/src/main/java/org/schabi/newpipe/database/Converters.kt @@ -1,11 +1,11 @@ package org.schabi.newpipe.database import androidx.room.TypeConverter -import org.schabi.newpipe.extractor.stream.StreamType -import org.schabi.newpipe.local.subscription.FeedGroupIcon import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset +import org.schabi.newpipe.extractor.stream.StreamType +import org.schabi.newpipe.local.subscription.FeedGroupIcon class Converters { /** diff --git a/app/src/main/java/org/schabi/newpipe/database/LocalItem.kt b/app/src/main/java/org/schabi/newpipe/database/LocalItem.kt index 50529610b..944b247bf 100644 --- a/app/src/main/java/org/schabi/newpipe/database/LocalItem.kt +++ b/app/src/main/java/org/schabi/newpipe/database/LocalItem.kt @@ -14,6 +14,6 @@ interface LocalItem { PLAYLIST_REMOTE_ITEM, PLAYLIST_STREAM_ITEM, - STATISTIC_STREAM_ITEM, + STATISTIC_STREAM_ITEM } } diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index d756df8b1..5861fa767 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -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 java.time.OffsetDateTime import org.schabi.newpipe.database.feed.model.FeedEntity import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity @@ -15,7 +16,6 @@ import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamStateEntity import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionEntity -import java.time.OffsetDateTime @Dao abstract class FeedDAO { diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt index beeedc62b..86568bc90 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedEntity.kt @@ -19,13 +19,17 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity entity = StreamEntity::class, parentColumns = [StreamEntity.STREAM_ID], childColumns = [STREAM_ID], - onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, + deferred = true ), ForeignKey( entity = SubscriptionEntity::class, parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], childColumns = [SUBSCRIPTION_ID], - onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, + deferred = true ) ] ) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt index b114a734c..6dac3c89c 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedGroupSubscriptionEntity.kt @@ -18,14 +18,18 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity entity = FeedGroupEntity::class, parentColumns = [FeedGroupEntity.ID], childColumns = [GROUP_ID], - onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, + deferred = true ), ForeignKey( entity = SubscriptionEntity::class, parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], childColumns = [SUBSCRIPTION_ID], - onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, + deferred = true ) ] ) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt index a19af9c45..fc0ee6742 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/model/FeedLastUpdatedEntity.kt @@ -4,10 +4,10 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey +import java.time.OffsetDateTime 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.subscription.SubscriptionEntity -import java.time.OffsetDateTime @Entity( tableName = FEED_LAST_UPDATED_TABLE, @@ -16,7 +16,9 @@ import java.time.OffsetDateTime entity = SubscriptionEntity::class, parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID], childColumns = [SUBSCRIPTION_ID], - onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, + deferred = true ) ] ) diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt index e6006a069..eee213453 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt @@ -29,7 +29,7 @@ data class SearchHistoryEntry @JvmOverloads constructor( @ColumnInfo(name = ID) @PrimaryKey(autoGenerate = true) - val id: Long = 0, + val id: Long = 0 ) { @Ignore diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.kt index db41e141c..deba7dd3a 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntity.kt @@ -11,12 +11,12 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.ForeignKey.Companion.CASCADE import androidx.room.Index +import java.time.OffsetDateTime import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.JOIN_STREAM_ID import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.STREAM_ACCESS_DATE import org.schabi.newpipe.database.history.model.StreamHistoryEntity.Companion.STREAM_HISTORY_TABLE import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID -import java.time.OffsetDateTime /** * @param streamUid the stream id this history item will refer to diff --git a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt index 27fc429f1..816b25c2a 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/history/model/StreamHistoryEntry.kt @@ -2,10 +2,10 @@ package org.schabi.newpipe.database.history.model import androidx.room.ColumnInfo import androidx.room.Embedded +import java.time.OffsetDateTime import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.util.image.ImageStrategy -import java.time.OffsetDateTime data class StreamHistoryEntry( @Embedded @@ -30,16 +30,15 @@ data class StreamHistoryEntry( accessDate.isEqual(other.accessDate) } - fun toStreamInfoItem(): StreamInfoItem = - StreamInfoItem( - streamEntity.serviceId, - streamEntity.url, - streamEntity.title, - streamEntity.streamType, - ).apply { - duration = streamEntity.duration - uploaderName = streamEntity.uploader - uploaderUrl = streamEntity.uploaderUrl - thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) - } + fun toStreamInfoItem(): StreamInfoItem = StreamInfoItem( + streamEntity.serviceId, + streamEntity.url, + streamEntity.title, + streamEntity.streamType + ).apply { + duration = streamEntity.duration + uploaderName = streamEntity.uploader + uploaderUrl = streamEntity.uploaderUrl + thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl) + } } diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.kt index 4ea4eb3a7..1f1862f4f 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.kt @@ -37,7 +37,7 @@ data class PlaylistEntity @JvmOverloads constructor( name = item.orderingName, isThumbnailPermanent = item.isThumbnailPermanent!!, thumbnailStreamId = item.thumbnailStreamId!!, - displayIndex = item.displayIndex!!, + displayIndex = item.displayIndex!! ) companion object { diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt index 3fa281e45..ce74678ca 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt @@ -9,13 +9,13 @@ package org.schabi.newpipe.database.stream import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Ignore +import java.time.OffsetDateTime import org.schabi.newpipe.database.LocalItem import org.schabi.newpipe.database.history.model.StreamHistoryEntity import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamStateEntity.Companion.STREAM_PROGRESS_MILLIS import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.util.image.ImageStrategy -import java.time.OffsetDateTime data class StreamStatisticsEntry( @Embedded diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt index d8c19c1e9..a6ab2c6cc 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt @@ -8,12 +8,12 @@ import androidx.room.Query import androidx.room.Transaction import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable +import java.time.OffsetDateTime 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 import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.util.StreamTypeUtil -import java.time.OffsetDateTime @Dao abstract class StreamDAO : BasicDAO { @@ -91,7 +91,6 @@ abstract class StreamDAO : BasicDAO { 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 = diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt index d9c160b89..067f666b6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.kt @@ -5,6 +5,8 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index import androidx.room.PrimaryKey +import java.io.Serializable +import java.time.OffsetDateTime import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL @@ -14,8 +16,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.player.playqueue.PlayQueueItem import org.schabi.newpipe.util.image.ImageStrategy -import java.io.Serializable -import java.time.OffsetDateTime @Entity( tableName = STREAM_TABLE, @@ -86,8 +86,12 @@ data class StreamEntity( @Ignore constructor(item: PlayQueueItem) : this( - serviceId = item.serviceId, url = item.url, title = item.title, - streamType = item.streamType, duration = item.duration, uploader = item.uploader, + serviceId = item.serviceId, + url = item.url, + title = item.title, + streamType = item.streamType, + duration = item.duration, + uploader = item.uploader, uploaderUrl = item.uploaderUrl, thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails) ) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 7ddfeb553..05f2b34a9 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -7,6 +7,7 @@ import androidx.core.content.ContextCompat import com.google.android.exoplayer2.ExoPlaybackException import com.google.android.exoplayer2.upstream.HttpDataSource import com.google.android.exoplayer2.upstream.Loader +import java.net.UnknownHostException import kotlinx.parcelize.Parcelize import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info @@ -28,7 +29,6 @@ import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentExcepti import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.player.mediasource.FailedMediaSource import org.schabi.newpipe.player.resolver.PlaybackResolver -import java.net.UnknownHostException /** * An error has occurred in the app. This class contains plain old parcelable data that can be used @@ -59,7 +59,7 @@ class ErrorInfo private constructor( * If present, this resource can alternatively be opened in browser (useful if NewPipe is * badly broken). */ - val openInBrowserUrl: String?, + val openInBrowserUrl: String? ) : Parcelable { @JvmOverloads @@ -68,7 +68,7 @@ class ErrorInfo private constructor( userAction: UserAction, request: String, serviceId: Int? = null, - openInBrowserUrl: String? = null, + openInBrowserUrl: String? = null ) : this( throwableToStringList(throwable), userAction, @@ -78,7 +78,7 @@ class ErrorInfo private constructor( isReportable(throwable), isRetryable(throwable), (throwable as? ReCaptchaException)?.url, - openInBrowserUrl, + openInBrowserUrl ) @JvmOverloads @@ -87,7 +87,7 @@ class ErrorInfo private constructor( userAction: UserAction, request: String, serviceId: Int? = null, - openInBrowserUrl: String? = null, + openInBrowserUrl: String? = null ) : this( throwableListToStringList(throwables), userAction, @@ -97,7 +97,7 @@ class ErrorInfo private constructor( throwables.any(::isReportable), throwables.isEmpty() || throwables.any(::isRetryable), throwables.firstNotNullOfOrNull { it as? ReCaptchaException }?.url, - openInBrowserUrl, + openInBrowserUrl ) // constructor to manually build ErrorInfo when no throwable is available @@ -118,7 +118,7 @@ class ErrorInfo private constructor( throwable: Throwable, userAction: UserAction, request: String, - info: Info?, + info: Info? ) : this(throwable, userAction, request, info?.serviceId, info?.url) @@ -127,7 +127,7 @@ class ErrorInfo private constructor( throwables: List, userAction: UserAction, request: String, - info: Info?, + info: Info? ) : this(throwables, userAction, request, info?.serviceId, info?.url) @@ -144,7 +144,7 @@ class ErrorInfo private constructor( class ErrorMessage( @StringRes private val stringRes: Int, - private vararg val formatArgs: String, + private vararg val formatArgs: String ) : Parcelable { fun getString(context: Context): String { return if (formatArgs.isEmpty()) { @@ -160,21 +160,19 @@ class ErrorInfo private constructor( const val SERVICE_NONE = "" - private fun getServiceName(serviceId: Int?) = - // not using getNameOfServiceById since we want to accept a nullable serviceId and we + private fun getServiceName(serviceId: Int?) = // not using getNameOfServiceById since we want to accept a nullable serviceId and we // want to default to SERVICE_NONE ServiceList.all().firstOrNull { it.serviceId == serviceId }?.serviceInfo?.name ?: SERVICE_NONE fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString()) - fun throwableListToStringList(throwableList: List) = - throwableList.map { it.stackTraceToString() }.toTypedArray() + fun throwableListToStringList(throwableList: List) = throwableList.map { it.stackTraceToString() }.toTypedArray() fun getMessage( throwable: Throwable?, action: UserAction?, - serviceId: Int?, + serviceId: Int? ): ErrorMessage { return when { // player exceptions @@ -193,18 +191,24 @@ class ErrorInfo private constructor( ErrorMessage(R.string.player_http_invalid_status, cause.responseCode.toString()) } } + cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException -> getMessage(throwable, action, serviceId) + throwable.type == ExoPlaybackException.TYPE_SOURCE -> ErrorMessage(R.string.player_stream_failure) + throwable.type == ExoPlaybackException.TYPE_UNEXPECTED -> ErrorMessage(R.string.player_recoverable_failure) + else -> ErrorMessage(R.string.player_unrecoverable_failure) } } + throwable is FailedMediaSource.FailedMediaSourceException -> getMessage(throwable.cause, action, serviceId) + throwable is PlaybackResolver.ResolverException -> ErrorMessage(R.string.player_stream_failure) @@ -220,34 +224,46 @@ class ErrorInfo private constructor( ) } ?: ErrorMessage(R.string.account_terminated) + throwable is AgeRestrictedContentException -> ErrorMessage(R.string.restricted_video_no_stream) + throwable is GeographicRestrictionException -> ErrorMessage(R.string.georestricted_content) + throwable is PaidContentException -> ErrorMessage(R.string.paid_content) + throwable is PrivateContentException -> ErrorMessage(R.string.private_content) + throwable is SoundCloudGoPlusContentException -> ErrorMessage(R.string.soundcloud_go_plus_content) + throwable is UnsupportedContentInCountryException -> ErrorMessage(R.string.unsupported_content_in_country) + throwable is YoutubeMusicPremiumContentException -> ErrorMessage(R.string.youtube_music_premium_content) + throwable is SignInConfirmNotBotException -> ErrorMessage(R.string.sign_in_confirm_not_bot_error, getServiceName(serviceId)) + throwable is ContentNotAvailableException -> ErrorMessage(R.string.content_not_available) // other extractor exceptions throwable is ContentNotSupportedException -> ErrorMessage(R.string.content_not_supported) + // ReCaptchas will be handled in a special way anyway throwable is ReCaptchaException -> ErrorMessage(R.string.recaptcha_request_toast) + // test this at the end as many exceptions could be a subclass of IOException throwable != null && throwable.isNetworkRelated -> ErrorMessage(R.string.network_error) + // an extraction exception unrelated to the network // is likely an issue with parsing the website throwable is ExtractionException -> @@ -256,16 +272,22 @@ class ErrorInfo private constructor( // user actions (in case the exception is null or unrecognizable) action == UserAction.UI_ERROR -> ErrorMessage(R.string.app_ui_crash) + action == UserAction.REQUESTED_COMMENTS -> ErrorMessage(R.string.error_unable_to_load_comments) + action == UserAction.SUBSCRIPTION_CHANGE -> ErrorMessage(R.string.subscription_change_failed) + action == UserAction.SUBSCRIPTION_UPDATE -> ErrorMessage(R.string.subscription_update_failed) + action == UserAction.LOAD_IMAGE -> ErrorMessage(R.string.could_not_load_thumbnails) + action == UserAction.DOWNLOAD_OPEN_DIALOG -> ErrorMessage(R.string.could_not_setup_download_menu) + else -> ErrorMessage(R.string.error_snackbar_message) } @@ -276,15 +298,19 @@ class ErrorInfo private constructor( // we don't have an exception, so this is a manually built error, which likely // indicates that it's important and is thus reportable null -> true + // the service explicitly said that content is not available (e.g. age restrictions, // video deleted, etc.), there is no use in letting users report it is ContentNotAvailableException -> false + // we know the content is not supported, no need to let the user report it is ContentNotSupportedException -> false + // happens often when there is no internet connection; we don't use // `throwable.isNetworkRelated` since any `IOException` would make that function // return true, but not all `IOException`s are network related is UnknownHostException -> false + // by default, this is an unexpected exception, which the user could report else -> true } @@ -294,8 +320,10 @@ class ErrorInfo private constructor( return when (throwable) { // we know the content is not available, retrying won't help is ContentNotAvailableException -> false + // we know the content is not supported, retrying won't help is ContentNotSupportedException -> false + // by default (including if throwable is null), enable retrying (though the retry // button will be shown only if a way to perform the retry is implemented) else -> true diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 4ec5f58c3..023d13e9d 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -11,16 +11,16 @@ import androidx.fragment.app.Fragment import com.jakewharton.rxbinding4.view.clicks import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable +import java.util.concurrent.TimeUnit import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.util.external_communication.ShareUtils -import java.util.concurrent.TimeUnit class ErrorPanelHelper( private val fragment: Fragment, rootView: View, - onRetry: Runnable?, + onRetry: Runnable? ) { private val context: Context = rootView.context!! diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index b358a5fd2..7facb5d85 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -46,7 +46,7 @@ class ErrorUtil { @JvmStatic fun openActivity(context: Context, errorInfo: ErrorInfo) { if (PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(MainActivity.KEY_IS_IN_BACKGROUND, true) + .getBoolean(MainActivity.KEY_IS_IN_BACKGROUND, true) ) { createNotification(context, errorInfo) } else { diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.kt b/app/src/main/java/org/schabi/newpipe/error/UserAction.kt index 2d2358310..b3f14e2da 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.kt +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.kt @@ -40,5 +40,5 @@ enum class UserAction(val message: String) { OPEN_INFO_ITEM_DIALOG("open info item dialog"), GETTING_MAIN_SCREEN_TAB("getting main screen tab"), PLAY_ON_POPUP("play on popup"), - SUBSCRIPTIONS("loading subscriptions"); + SUBSCRIPTIONS("loading subscriptions") } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ItemViewMode.kt b/app/src/main/java/org/schabi/newpipe/info_list/ItemViewMode.kt index 703191bb9..899223afa 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/ItemViewMode.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/ItemViewMode.kt @@ -13,14 +13,17 @@ enum class ItemViewMode { * Default mode. */ AUTO, + /** * Full width list item with thumb on the left and two line title & uploader in right. */ LIST, + /** * Grid mode places two cards per row. */ GRID, + /** * A full width card in phone - portrait. */ diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentAdapter.kt b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentAdapter.kt index 869bf6f48..9b6005f65 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentAdapter.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentAdapter.kt @@ -2,8 +2,8 @@ package org.schabi.newpipe.info_list import android.util.Log import com.xwray.groupie.GroupieAdapter -import org.schabi.newpipe.extractor.stream.StreamInfo import kotlin.math.max +import org.schabi.newpipe.extractor.stream.StreamInfo /** * Custom RecyclerView.Adapter/GroupieAdapter for [StreamSegmentItem] for handling selection state. diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt index 1e52d3168..a7ca3bd68 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamSegmentItem.kt @@ -41,7 +41,10 @@ class StreamSegmentItem( viewHolder.root.findViewById(R.id.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.setOnLongClickListener { + onClick.onItemLongClick(this, item.startTimeSeconds) + true + } viewHolder.root.isSelected = isSelected } diff --git a/app/src/main/java/org/schabi/newpipe/ktx/View.kt b/app/src/main/java/org/schabi/newpipe/ktx/View.kt index cf9e4baef..432b974cb 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -43,8 +43,14 @@ fun View.animate( if (DEBUG) { val id = runCatching { resources.getResourceEntryName(id) }.getOrDefault(id.toString()) val msg = String.format( - "%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit, - javaClass.simpleName, id, animationType, duration, delay, execOnEnd + "%8s → [%s:%s] [%s %s:%s] execOnEnd=%s", + enterOrExit, + javaClass.simpleName, + id, + animationType, + duration, + delay, + execOnEnd ) Log.d(TAG, "animate(): $msg") } @@ -287,5 +293,9 @@ private class HideAndExecOnEndListener(private val view: View, execOnEnd: Runnab } enum class AnimationType { - ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA + ALPHA, + SCALE_AND_ALPHA, + LIGHT_SCALE_AND_ALPHA, + SLIDE_AND_ALPHA, + LIGHT_SLIDE_AND_ALPHA } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 8a1de01c9..3e3a47f57 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -7,6 +7,9 @@ import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.schedulers.Schedulers +import java.time.LocalDate +import java.time.OffsetDateTime +import java.time.ZoneOffset import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.database.feed.model.FeedEntity @@ -18,9 +21,6 @@ import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.local.subscription.FeedGroupIcon -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset class FeedDatabaseManager(context: Context) { private val database = NewPipeDatabase.getInstance(context) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index bbad7f689..ac076f1b8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -53,6 +53,8 @@ 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 java.time.OffsetDateTime +import java.util.function.Consumer import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity @@ -81,8 +83,6 @@ import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams import org.schabi.newpipe.util.ThemeHelper.getItemViewMode import org.schabi.newpipe.util.ThemeHelper.resolveDrawable import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout -import java.time.OffsetDateTime -import java.util.function.Consumer class FeedFragment : BaseStateFragment() { private var _feedBinding: FragmentFeedBinding? = null @@ -91,7 +91,10 @@ class FeedFragment : BaseStateFragment() { private val disposables = CompositeDisposable() private lateinit var viewModel: FeedViewModel - @State @JvmField var listState: Parcelable? = null + + @State + @JvmField + var listState: Parcelable? = null private var groupId = FeedGroupEntity.GROUP_ALL_ID private var groupName = "" @@ -149,7 +152,6 @@ class FeedFragment : BaseStateFragment() { if (newState == RecyclerView.SCROLL_STATE_IDLE && !recyclerView.canScrollVertically(-1) ) { - if (tryGetNewItemsLoadedButton()?.isVisible == true) { hideNewItemsLoaded(true) } @@ -387,8 +389,13 @@ class FeedFragment : BaseStateFragment() { if (item is StreamItem && !isRefreshing) { val stream = item.streamWithState.stream NavigationHelper.openVideoDetailFragment( - requireContext(), fm, - stream.serviceId, stream.url, stream.title, null, false + requireContext(), + fm, + stream.serviceId, + stream.url, + stream.title, + null, + false ) } } @@ -500,7 +507,8 @@ class FeedFragment : BaseStateFragment() { ) { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val isFastFeedModeEnabled = sharedPreferences.getBoolean( - getString(R.string.feed_use_dedicated_fetch_method_key), false + getString(R.string.feed_use_dedicated_fetch_method_key), + false ) val builder = AlertDialog.Builder(requireContext()) @@ -535,7 +543,8 @@ class FeedFragment : BaseStateFragment() { private fun updateRelativeTimeViews() { updateRefreshViewState() groupAdapter.notifyItemRangeChanged( - 0, groupAdapter.itemCount, + 0, + groupAdapter.itemCount, StreamItem.UPDATE_RELATIVE_TIME ) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt index 665ebbe43..6d6bc9007 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedState.kt @@ -1,8 +1,8 @@ package org.schabi.newpipe.local.feed import androidx.annotation.StringRes -import org.schabi.newpipe.local.feed.item.StreamItem import java.time.OffsetDateTime +import org.schabi.newpipe.local.feed.item.StreamItem sealed class FeedState { data class ProgressState( diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index f916db2b5..7e781b30f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -14,6 +14,8 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.functions.Function6 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import java.time.OffsetDateTime +import java.util.concurrent.TimeUnit import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity @@ -25,8 +27,6 @@ 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.SuccessResultEvent import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT -import java.time.OffsetDateTime -import java.util.concurrent.TimeUnit class FeedViewModel( private val application: Application, @@ -64,8 +64,14 @@ class FeedViewModel( feedDatabaseManager.notLoadedCount(groupId), feedDatabaseManager.oldestSubscriptionUpdate(groupId), - Function6 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, t4: Boolean, - t5: Long, t6: List -> + Function6 { + t1: FeedEventManager.Event, + t2: Boolean, + t3: Boolean, + t4: Boolean, + t5: Long, + t6: List + -> return@Function6 CombineResultEventHolder(t1, t2, t3, t4, t5, t6.firstOrNull()) } ) @@ -73,12 +79,13 @@ class FeedViewModel( .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .map { (event, showPlayedItems, showPartiallyPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> - val streamItems = if (event is SuccessResultEvent || event is IdleEvent) + val streamItems = if (event is SuccessResultEvent || event is IdleEvent) { feedDatabaseManager .getStreams(groupId, showPlayedItems, showPartiallyPlayedItems, showFutureItems) .blockingGet(arrayListOf()) - else + } else { arrayListOf() + } CombineResultDataHolder(event, streamItems, notLoadedCount, oldestUpdate) } @@ -150,17 +157,14 @@ class FeedViewModel( fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application) companion object { - private fun getShowPlayedItemsFromPreferences(context: Context) = - PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.feed_show_watched_items_key), true) + private fun getShowPlayedItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.feed_show_watched_items_key), true) - private fun getShowPartiallyPlayedItemsFromPreferences(context: Context) = - PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.feed_show_partially_watched_items_key), true) + private fun getShowPartiallyPlayedItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.feed_show_partially_watched_items_key), true) - private fun getShowFutureItemsFromPreferences(context: Context) = - PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.feed_show_future_items_key), true) + private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.feed_show_future_items_key), true) fun getFactory(context: Context, groupId: Long) = viewModelFactory { initializer { diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index 4a071d6df..e367961f1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -6,6 +6,8 @@ import android.view.View import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import com.xwray.groupie.viewbinding.BindableItem +import java.util.concurrent.TimeUnit +import java.util.function.Consumer import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.database.stream.StreamWithState @@ -20,8 +22,6 @@ 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 java.util.concurrent.TimeUnit -import java.util.function.Consumer data class StreamItem( val streamWithState: StreamWithState, @@ -132,6 +132,7 @@ data class StreamItem( viewsAndDate.isEmpty() -> uploadDate!! else -> Localization.concatenateStrings(viewsAndDate, uploadDate) } + else -> viewsAndDate } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 7ce117fcd..d2d16a755 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -42,7 +42,9 @@ class NotificationHelper(val context: Context) { fun displayNewStreamsNotifications(data: FeedUpdateInfo) { val newStreams = data.newStreams val summary = context.resources.getQuantityString( - R.plurals.new_streams, newStreams.size, newStreams.size + R.plurals.new_streams, + newStreams.size, + newStreams.size ) val summaryBuilder = NotificationCompat.Builder( context, diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 6fe311fb0..d1fd29945 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -16,6 +16,7 @@ import androidx.work.WorkerParameters import androidx.work.rxjava3.RxWorker import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single +import java.util.concurrent.TimeUnit import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.error.ErrorInfo @@ -23,7 +24,6 @@ import org.schabi.newpipe.error.ErrorUtil import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.local.feed.service.FeedLoadManager import org.schabi.newpipe.local.feed.service.FeedLoadService -import java.util.concurrent.TimeUnit /* * Worker which checks for new streams of subscribed channels @@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit */ class NotificationWorker( appContext: Context, - workerParams: WorkerParameters, + workerParams: WorkerParameters ) : RxWorker(appContext, workerParams) { private val notificationHelper by lazy { @@ -95,9 +95,8 @@ class NotificationWorker( private val TAG = NotificationWorker::class.java.simpleName private const val WORK_TAG = App.PACKAGE_NAME + "_streams_notifications" - private fun areNotificationsEnabled(context: Context) = - NotificationHelper.areNewStreamsNotificationsEnabled(context) && - NotificationHelper.areNotificationsEnabledOnDevice(context) + private fun areNotificationsEnabled(context: Context) = NotificationHelper.areNewStreamsNotificationsEnabled(context) && + NotificationHelper.areNotificationsEnabledOnDevice(context) /** * Schedules a task for the [NotificationWorker] diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt index b1027e21a..6d5f12b2b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/ScheduleOptions.kt @@ -2,9 +2,9 @@ package org.schabi.newpipe.local.feed.notifications import android.content.Context import androidx.preference.PreferenceManager +import java.util.concurrent.TimeUnit import org.schabi.newpipe.R import org.schabi.newpipe.ktx.getStringSafe -import java.util.concurrent.TimeUnit /** * Information for the Scheduler which checks for new streams. diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt index 1c2826e7a..952a59b9a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedEventManager.kt @@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service import androidx.annotation.StringRes import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.processors.BehaviorProcessor -import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent import java.util.concurrent.atomic.AtomicBoolean +import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent object FeedEventManager { private var processor: BehaviorProcessor = BehaviorProcessor.create() diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index 9b0f177d5..3090a92d4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -11,6 +11,10 @@ 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 java.time.OffsetDateTime +import java.time.ZoneOffset +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.subscription.NotificationMode @@ -27,10 +31,6 @@ import org.schabi.newpipe.util.ChannelTabHelper import org.schabi.newpipe.util.ExtractorHelper.getChannelInfo import org.schabi.newpipe.util.ExtractorHelper.getChannelTab import org.schabi.newpipe.util.ExtractorHelper.getMoreChannelTabItems -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicInteger class FeedLoadManager(private val context: Context) { @@ -60,7 +60,7 @@ class FeedLoadManager(private val context: Context) { */ fun startLoading( groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - ignoreOutdatedThreshold: Boolean = false, + ignoreOutdatedThreshold: Boolean = false ): Single>> { val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val useFeedExtractor = defaultSharedPreferences.getBoolean( @@ -85,9 +85,12 @@ class FeedLoadManager(private val context: Context) { FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions( outdatedThreshold ) + GROUP_NOTIFICATION_ENABLED -> feedDatabaseManager.outdatedSubscriptionsWithNotificationMode( - outdatedThreshold, NotificationMode.ENABLED + outdatedThreshold, + NotificationMode.ENABLED ) + else -> feedDatabaseManager.outdatedSubscriptionsForGroup(groupId, outdatedThreshold) } @@ -186,7 +189,8 @@ class FeedLoadManager(private val context: Context) { val channelInfo = getChannelInfo( subscriptionEntity.serviceId, - subscriptionEntity.url, true + subscriptionEntity.url, + true ) .onErrorReturn(storeOriginalErrorAndRethrow) .blockingGet() @@ -216,7 +220,8 @@ class FeedLoadManager(private val context: Context) { ) { val infoItemsPage = getMoreChannelTabItems( subscriptionEntity.serviceId, - linkHandler, channelTabInfo.nextPage + linkHandler, + channelTabInfo.nextPage ) .blockingGet() @@ -234,7 +239,7 @@ class FeedLoadManager(private val context: Context) { subscriptionEntity, originalInfo!!, streams!!, - errors, + errors ) ) } catch (e: Throwable) { @@ -305,6 +310,7 @@ class FeedLoadManager(private val context: Context) { feedDatabaseManager.markAsOutdated(info.uid) } } + notification.isOnError -> { val error = notification.error feedResultsHolder.addError(error!!) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt index 4aa825ca8..48ad5df94 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadService.kt @@ -36,13 +36,13 @@ 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 java.util.concurrent.TimeUnit import org.schabi.newpipe.App import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent -import java.util.concurrent.TimeUnit class FeedLoadService : Service() { companion object { @@ -94,7 +94,8 @@ class FeedLoadService : Service() { .doOnSubscribe { startForeground(NOTIFICATION_ID, notificationBuilder.build()) } - .subscribe { _, error: Throwable? -> // explicitly mark error as nullable + .subscribe { _, error: Throwable? -> + // explicitly mark error as nullable if (error != null) { Log.e(TAG, "Error while storing result", error) handleError(error) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt index 703f593ad..2aedf0925 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadState.kt @@ -3,5 +3,5 @@ package org.schabi.newpipe.local.feed.service data class FeedLoadState( val updateDescription: String, val maxProgress: Int, - val currentProgress: Int, + val currentProgress: Int ) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt index b44eec353..fb4a27913 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt @@ -25,13 +25,13 @@ data class FeedUpdateInfo( val description: String?, val subscriberCount: Long?, val streams: List, - val errors: List, + val errors: List ) { constructor( subscription: SubscriptionEntity, info: Info, streams: List, - errors: List, + errors: List ) : this( uid = subscription.uid, notificationMode = subscription.notificationMode, @@ -46,7 +46,7 @@ data class FeedUpdateInfo( description = (info as? ChannelInfo)?.description, subscriberCount = (info as? ChannelInfo)?.subscriberCount, streams = streams, - errors = errors, + errors = errors ) /** diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt index 9c7f1ba09..a6c3561ec 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/ExportPlaylist.kt @@ -44,7 +44,6 @@ private fun exportJustUrls(playlist: List): String { } private fun exportAsYoutubeTempPlaylist(playlist: List): String { - val videoIDs = playlist.asReversed().asSequence() .mapNotNull { getYouTubeId(it.streamEntity.url) } .take(50) // YouTube limitation: temp playlists can't have more than 50 items @@ -64,6 +63,5 @@ private val linkHandler: YoutubeStreamLinkHandlerFactory = YoutubeStreamLinkHand * @return the video id */ private fun getYouTubeId(url: String): String? { - return runCatching { linkHandler.getId(url) }.getOrNull() } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 8e758adef..28abe4cf9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -27,6 +27,9 @@ import com.xwray.groupie.GroupAdapter import com.xwray.groupie.Section import com.xwray.groupie.viewbinding.GroupieViewHolder import io.reactivex.rxjava3.disposables.CompositeDisposable +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID import org.schabi.newpipe.databinding.DialogTitleBinding @@ -62,9 +65,6 @@ import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.external_communication.ShareUtils -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale class SubscriptionFragment : BaseStateFragment() { private var _binding: FragmentSubscriptionBinding? = null @@ -276,10 +276,13 @@ class SubscriptionFragment : BaseStateFragment() { when (item) { is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupCardGridItem -> NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupAddNewItem -> FeedGroupDialog.newInstance().show(fm, null) + is FeedGroupAddNewGridItem -> FeedGroupDialog.newInstance().show(fm, null) } @@ -294,6 +297,7 @@ class SubscriptionFragment : BaseStateFragment() { when (item) { is FeedGroupCardItem -> FeedGroupDialog.newInstance(item.groupId).show(fm, null) + is FeedGroupCardGridItem -> FeedGroupDialog.newInstance(item.groupId).show(fm, null) } @@ -309,7 +313,7 @@ class SubscriptionFragment : BaseStateFragment() { title = getString(R.string.feed_groups_header_title), onSortClicked = ::openReorderDialog, onToggleListViewModeClicked = ::toggleListViewMode, - listViewMode = viewModel.getListViewMode(), + listViewMode = viewModel.getListViewMode() ) add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) @@ -342,9 +346,14 @@ class SubscriptionFragment : BaseStateFragment() { val actions = DialogInterface.OnClickListener { _, i -> when (i) { 0 -> ShareUtils.shareText( - requireContext(), selectedItem.name, selectedItem.url, selectedItem.thumbnails + requireContext(), + selectedItem.name, + selectedItem.url, + selectedItem.thumbnails ) + 1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url) + 2 -> deleteChannel(selectedItem) } } @@ -374,7 +383,9 @@ class SubscriptionFragment : BaseStateFragment() { private val listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, - selectedItem.serviceId, selectedItem.url, selectedItem.name + selectedItem.serviceId, + selectedItem.url, + selectedItem.name ) override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem) @@ -404,6 +415,7 @@ class SubscriptionFragment : BaseStateFragment() { itemsListState = null } } + is SubscriptionState.ErrorState -> { result.error?.let { showError(ErrorInfo(result.error, UserAction.SOMETHING_ELSE, "Subscriptions")) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index c0783e812..2918ad5fb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -37,13 +37,16 @@ class SubscriptionManager(context: Context) { filterQuery.isNotEmpty() -> { return if (showOnlyUngrouped) { subscriptionTable.getSubscriptionsOnlyUngroupedFiltered( - currentGroupId, filterQuery + currentGroupId, + filterQuery ) } else { subscriptionTable.getSubscriptionsFiltered(filterQuery) } } + showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId) + else -> subscriptionTable.getAll() } } @@ -67,19 +70,18 @@ class SubscriptionManager(context: Context) { return listEntities } - fun updateChannelInfo(info: ChannelInfo): Completable = - subscriptionTable.getSubscription(info.serviceId, info.url) - .flatMapCompletable { - Completable.fromRunnable { - it.apply { - name = info.name - avatarUrl = ImageStrategy.imageListToDbUrl(info.avatars) - description = info.description - subscriberCount = info.subscriberCount - } - subscriptionTable.update(it) + fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url) + .flatMapCompletable { + Completable.fromRunnable { + it.apply { + name = info.name + avatarUrl = ImageStrategy.imageListToDbUrl(info.avatars) + description = info.description + subscriberCount = info.subscriberCount } + subscriptionTable.update(it) } + } fun updateNotificationMode(serviceId: Int, url: String, @NotificationMode mode: Int): Completable { return subscriptionTable().getSubscription(serviceId, url) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt index dfad60c3f..fc28f8e59 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt @@ -9,6 +9,7 @@ import com.xwray.groupie.Group import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import java.util.concurrent.TimeUnit import org.schabi.newpipe.info_list.ItemViewMode import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.subscription.item.ChannelItem @@ -16,7 +17,6 @@ import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT import org.schabi.newpipe.util.ThemeHelper.getItemViewMode -import java.util.concurrent.TimeUnit class SubscriptionViewModel(application: Application) : AndroidViewModel(application) { private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index 71c90e3c1..ab856278f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -23,6 +23,7 @@ import com.livefront.bridge.Bridge import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.OnItemClickListener import com.xwray.groupie.Section +import java.io.Serializable import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.databinding.DialogFeedGroupCreateBinding @@ -40,7 +41,6 @@ import org.schabi.newpipe.local.subscription.item.PickerIconItem import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem import org.schabi.newpipe.util.DeviceUtils import org.schabi.newpipe.util.ThemeHelper -import java.io.Serializable class FeedGroupDialog : DialogFragment(), BackPressable { private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null @@ -61,16 +61,41 @@ class FeedGroupDialog : DialogFragment(), BackPressable { data object DeleteScreen : ScreenState() } - @State @JvmField var selectedIcon: FeedGroupIcon? = null - @State @JvmField var selectedSubscriptions: HashSet = HashSet() - @State @JvmField var wasSubscriptionSelectionChanged: Boolean = false - @State @JvmField var currentScreen: ScreenState = InitialScreen + @State + @JvmField + var selectedIcon: FeedGroupIcon? = null - @State @JvmField var subscriptionsListState: Parcelable? = null - @State @JvmField var iconsListState: Parcelable? = null - @State @JvmField var wasSearchSubscriptionsVisible = false - @State @JvmField var subscriptionsCurrentSearchQuery = "" - @State @JvmField var subscriptionsShowOnlyUngrouped = false + @State + @JvmField + var selectedSubscriptions: HashSet = HashSet() + + @State + @JvmField + var wasSubscriptionSelectionChanged: Boolean = false + + @State + @JvmField + var currentScreen: ScreenState = InitialScreen + + @State + @JvmField + var subscriptionsListState: Parcelable? = null + + @State + @JvmField + var iconsListState: Parcelable? = null + + @State + @JvmField + var wasSearchSubscriptionsVisible = false + + @State + @JvmField + var subscriptionsCurrentSearchQuery = "" + + @State + @JvmField + var subscriptionsShowOnlyUngrouped = false private val subscriptionMainSection = Section() private val subscriptionEmptyFooter = Section() @@ -153,8 +178,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable { itemAnimator = null adapter = subscriptionGroupAdapter layoutManager = GridLayoutManager( - requireContext(), subscriptionGroupAdapter.spanCount, - RecyclerView.VERTICAL, false + requireContext(), + subscriptionGroupAdapter.spanCount, + RecyclerView.VERTICAL, + false ).apply { spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup } @@ -362,7 +389,8 @@ class FeedGroupDialog : DialogFragment(), BackPressable { val selectedCount = this.selectedSubscriptions.size val selectedCountText = resources.getQuantityString( R.plurals.feed_group_dialog_selection_count, - selectedCount, selectedCount + selectedCount, + selectedCount ) feedGroupCreateBinding.selectedSubscriptionCountView.text = selectedCountText feedGroupCreateBinding.subscriptionsHeaderInfo.text = selectedCountText diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index 292bda394..d8eac2492 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -55,7 +55,8 @@ class FeedGroupDialogViewModel( private var subscriptionsDisposable = Flowable .combineLatest( - subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId) + subscriptionsFlowable, + feedDatabaseManager.subscriptionIdsForGroup(groupId) ) { t1: List, t2: List -> t1 to t2.toSet() } .subscribeOn(Schedulers.io()) .subscribe(mutableSubscriptionsLiveData::postValue) @@ -125,7 +126,10 @@ class FeedGroupDialogViewModel( ) = viewModelFactory { initializer { FeedGroupDialogViewModel( - context.applicationContext, groupId, initialQuery, initialShowOnlyUngrouped + context.applicationContext, + groupId, + initialQuery, + initialShowOnlyUngrouped ) } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt index c087da464..11f034ba0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt @@ -15,6 +15,7 @@ import com.evernote.android.state.State import com.livefront.bridge.Bridge import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.TouchCallback +import java.util.Collections import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.databinding.DialogFeedGroupReorderBinding @@ -22,7 +23,6 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem import org.schabi.newpipe.util.ThemeHelper -import java.util.Collections class FeedGroupReorderDialog : DialogFragment() { private var _binding: DialogFeedGroupReorderBinding? = null diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index bc39dafe6..7946de693 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -43,7 +43,10 @@ class ChannelItem( gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } - viewHolder.root.setOnLongClickListener { held(infoItem); true } + viewHolder.root.setOnLongClickListener { + held(infoItem) + true + } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt index 5a9d6887b..c78801c03 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt @@ -10,7 +10,7 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon data class FeedGroupCardGridItem( val groupId: Long = FeedGroupEntity.GROUP_ALL_ID, val name: String, - val icon: FeedGroupIcon, + val icon: FeedGroupIcon ) : BindableItem() { constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index 0453f297a..8682adc43 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -18,7 +18,7 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi * and provides some abstract methods to make it easier separating the logic from the UI. */ abstract class BasePlayerGestureListener( - private val playerUi: VideoPlayerUi, + private val playerUi: VideoPlayerUi ) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { protected val player: Player = playerUi.player @@ -86,8 +86,9 @@ abstract class BasePlayerGestureListener( // /////////////////////////////////////////////////////////////////// override fun onDown(e: MotionEvent): Boolean { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onDown called with e = [$e]") + } if (isDoubleTapping && isDoubleTapEnabled) { doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) @@ -108,8 +109,9 @@ abstract class BasePlayerGestureListener( } override fun onDoubleTap(e: MotionEvent): Boolean { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onDoubleTap called with e = [$e]") + } onDoubleTap(e, getDisplayPortion(e)) return true @@ -136,8 +138,9 @@ abstract class BasePlayerGestureListener( private fun startMultiDoubleTap(e: MotionEvent) { if (!isDoubleTapping) { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "startMultiDoubleTap called with e = [$e]") + } keepInDoubleTapMode() doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) @@ -145,8 +148,9 @@ abstract class BasePlayerGestureListener( } fun keepInDoubleTapMode() { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "keepInDoubleTapMode called") + } isDoubleTapping = true doubleTapHandler.removeCallbacksAndMessages(DOUBLE_TAP) @@ -161,8 +165,9 @@ abstract class BasePlayerGestureListener( } fun endMultiDoubleTap() { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "endMultiDoubleTap called") + } isDoubleTapping = false doubleTapHandler.removeCallbacksAndMessages(DOUBLE_TAP) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt index 684f6d326..c5d483628 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt @@ -1,5 +1,9 @@ package org.schabi.newpipe.player.gesture enum class DisplayPortion { - LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF + LEFT, + MIDDLE, + RIGHT, + LEFT_HALF, + RIGHT_HALF } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index ff0bb269d..7cc9ba224 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -8,6 +8,7 @@ import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible +import kotlin.math.abs import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.ktx.AnimationType @@ -17,7 +18,6 @@ import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi import org.schabi.newpipe.util.ThemeHelper.getAndroidDimenPx -import kotlin.math.abs /** * GestureListener for the player @@ -42,24 +42,29 @@ class MainPlayerGestureListener( v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) true } + MotionEvent.ACTION_UP -> { v.parent?.requestDisallowInterceptTouchEvent(false) false } + else -> true } } override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + } - if (isDoubleTapping) + if (isDoubleTapping) { return true + } super.onSingleTapConfirmed(e) - if (player.currentState != Player.STATE_BLOCKED) + if (player.currentState != Player.STATE_BLOCKED) { onSingleTap() + } return true } @@ -195,6 +200,7 @@ class MainPlayerGestureListener( when (PlayerHelper.getActionForRightGestureSide(player.context)) { player.context.getString(R.string.volume_control_key) -> onScrollVolume(distanceY) + player.context.getString(R.string.brightness_control_key) -> onScrollBrightness(distanceY) } @@ -202,6 +208,7 @@ class MainPlayerGestureListener( when (PlayerHelper.getActionForLeftGestureSide(player.context)) { player.context.getString(R.string.volume_control_key) -> onScrollVolume(distanceY) + player.context.getString(R.string.brightness_control_key) -> onScrollBrightness(distanceY) } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index 0b94bf364..60752652e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -5,17 +5,17 @@ import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import androidx.core.view.isVisible -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.ktx.AnimationType -import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.ui.PopupPlayerUi import kotlin.math.abs import kotlin.math.hypot import kotlin.math.max import kotlin.math.min +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.ui.PopupPlayerUi class PopupPlayerGestureListener( - private val playerUi: PopupPlayerUi, + private val playerUi: PopupPlayerUi ) : BasePlayerGestureListener(playerUi) { private var isMoving = false @@ -205,13 +205,16 @@ class PopupPlayerGestureListener( } override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + } - if (isDoubleTapping) + if (isDoubleTapping) { return true - if (player.exoPlayerIsNull()) + } + if (player.exoPlayerIsNull()) { return false + } onSingleTap() return true diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt index d221d704b..6b59f683a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserImpl.kt @@ -17,6 +17,7 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers +import java.util.function.Consumer import org.schabi.newpipe.MainActivity.DEBUG import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R @@ -37,7 +38,6 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.image.ImageStrategy -import java.util.function.Consumer /** * This class is used to cleanly separate the Service implementation (in @@ -47,7 +47,8 @@ import java.util.function.Consumer */ class MediaBrowserImpl( private val context: Context, - notifyChildrenChanged: Consumer, // parentId + // parentId + notifyChildrenChanged: Consumer ) { private val packageValidator = PackageValidator(context) private val database = NewPipeDatabase.getInstance(context) @@ -89,7 +90,8 @@ class MediaBrowserImpl( val extras = Bundle() extras.putBoolean( - MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true + MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, + true ) return MediaBrowserServiceCompat.BrowserRoot(ID_ROOT, extras) } @@ -137,7 +139,7 @@ class MediaBrowserImpl( ) } - when (/*val uriType = */path.removeAt(0)) { + when (path.removeAt(0)) { ID_BOOKMARKS -> { if (path.isEmpty()) { return populateBookmarks() @@ -204,12 +206,12 @@ class MediaBrowserImpl( val extras = Bundle() extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, - context.resources.getString(R.string.tab_bookmarks), + context.resources.getString(R.string.tab_bookmarks) ) builder.setExtras(extras) return MediaBrowserCompat.MediaItem( builder.build(), - MediaBrowserCompat.MediaItem.FLAG_BROWSABLE, + MediaBrowserCompat.MediaItem.FLAG_BROWSABLE ) } @@ -266,7 +268,7 @@ class MediaBrowserImpl( private fun createLocalPlaylistStreamMediaItem( playlistId: Long, item: PlaylistStreamEntry, - index: Int, + index: Int ): MediaBrowserCompat.MediaItem { val builder = MediaDescriptionCompat.Builder() builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index)) @@ -283,7 +285,7 @@ class MediaBrowserImpl( private fun createRemotePlaylistStreamMediaItem( playlistId: Long, item: StreamInfoItem, - index: Int, + index: Int ): MediaBrowserCompat.MediaItem { val builder = MediaDescriptionCompat.Builder() builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index)) @@ -303,7 +305,7 @@ class MediaBrowserImpl( private fun createMediaIdForPlaylistIndex( isRemote: Boolean, playlistId: Long, - index: Int, + index: Int ): String { return buildLocalPlaylistItemMediaId(isRemote, playlistId) .appendPath(index.toString()) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt index 072a8f332..890c83cfa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserPlaybackPreparer.kt @@ -14,6 +14,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers +import java.util.function.BiConsumer +import java.util.function.Consumer import org.schabi.newpipe.MainActivity import org.schabi.newpipe.NewPipeDatabase import org.schabi.newpipe.R @@ -30,8 +32,6 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue import org.schabi.newpipe.util.ChannelTabHelper import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.NavigationHelper -import java.util.function.BiConsumer -import java.util.function.Consumer /** * This class is used to cleanly separate the Service implementation (in @@ -51,7 +51,7 @@ class MediaBrowserPlaybackPreparer( private val context: Context, private val setMediaSessionError: BiConsumer, // error string, error code private val clearMediaSessionError: Runnable, - private val onPrepare: Consumer, + private val onPrepare: Consumer ) : PlaybackPreparer { private val database = NewPipeDatabase.getInstance(context) private var disposable: Disposable? = null @@ -146,7 +146,7 @@ class MediaBrowserPlaybackPreparer( throw parseError(mediaId) } - return when (/*val uriType = */path.removeAt(0)) { + return when (path.removeAt(0)) { ID_BOOKMARKS -> extractPlayQueueFromPlaylistMediaId( mediaId, path, @@ -172,7 +172,7 @@ class MediaBrowserPlaybackPreparer( private fun extractPlayQueueFromPlaylistMediaId( mediaId: String, path: MutableList, - url: String?, + url: String? ): Single { if (path.isEmpty()) { throw parseError(mediaId) @@ -185,10 +185,11 @@ class MediaBrowserPlaybackPreparer( } val playlistId = path[0].toLong() val index = path[1].toInt() - return if (playlistType == ID_LOCAL) + return if (playlistType == ID_LOCAL) { extractLocalPlayQueue(playlistId, index) - else + } else { extractRemotePlayQueue(playlistId, index) + } } ID_URL -> { @@ -208,7 +209,7 @@ class MediaBrowserPlaybackPreparer( @Throws(ContentNotAvailableException::class) private fun extractPlayQueueFromHistoryMediaId( mediaId: String, - path: List, + path: List ): Single { if (path.size != 1) { throw parseError(mediaId) @@ -229,14 +230,14 @@ class MediaBrowserPlaybackPreparer( private fun extractPlayQueueFromInfoItemMediaId( mediaId: String, path: List, - url: String, + url: String ): Single { if (path.size != 2) { throw parseError(mediaId) } val serviceId = path[1].toInt() - return when (/*val infoItemType = */infoItemTypeFromString(path[0])) { + return when (infoItemTypeFromString(path[0])) { InfoType.STREAM -> ExtractorHelper.getStreamInfo(serviceId, url, false) .map { SinglePlayQueue(it) } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt index 05719b6d4..05c94e990 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt @@ -30,9 +30,9 @@ import android.support.v4.media.session.MediaSessionCompat import android.util.Log import androidx.core.app.NotificationManagerCompat import androidx.media.MediaBrowserServiceCompat -import org.schabi.newpipe.BuildConfig import java.security.MessageDigest import java.security.NoSuchAlgorithmException +import org.schabi.newpipe.BuildConfig /** * Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat]. @@ -94,18 +94,22 @@ internal class PackageValidator(context: Context) { val isCallerKnown = when { // If it's our own app making the call, allow it. callingUid == Process.myUid() -> true + // If the system is making the call, allow it. callingUid == Process.SYSTEM_UID -> true + // If the app was signed by the same certificate as the platform itself, also allow it. callerSignature == platformSignature -> true - /** + + /* * [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and * while it isn't required to allow these apps to connect to a * [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps * such as Android TV and the Google Assistant. */ callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true - /** + + /* * If the calling app has a notification listener it is able to retrieve notifications * and can connect to an active [MediaSessionCompat]. * @@ -169,11 +173,10 @@ internal class PackageValidator(context: Context) { */ @Suppress("deprecation") @SuppressLint("PackageManagerGetSignatures") - private fun getPackageInfo(callingPackage: String): PackageInfo? = - packageManager.getPackageInfo( - callingPackage, - PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS - ) + private fun getPackageInfo(callingPackage: String): PackageInfo? = packageManager.getPackageInfo( + callingPackage, + PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS + ) /** * Gets the signature of a given package's [PackageInfo]. @@ -185,23 +188,21 @@ internal class PackageValidator(context: Context) { * returns `null` as the signature. */ @Suppress("deprecation") - private fun getSignature(packageInfo: PackageInfo): String? = - if (packageInfo.signatures == null || packageInfo.signatures!!.size != 1) { - // Security best practices dictate that an app should be signed with exactly one (1) - // signature. Because of this, if there are multiple signatures, reject it. - null - } else { - val certificate = packageInfo.signatures!![0].toByteArray() - getSignatureSha256(certificate) - } + private fun getSignature(packageInfo: PackageInfo): String? = if (packageInfo.signatures == null || packageInfo.signatures!!.size != 1) { + // Security best practices dictate that an app should be signed with exactly one (1) + // signature. Because of this, if there are multiple signatures, reject it. + null + } else { + val certificate = packageInfo.signatures!![0].toByteArray() + getSignatureSha256(certificate) + } /** * Finds the Android platform signing key signature. This key is never null. */ - private fun getSystemSignature(): String = - getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo -> - getSignature(platformInfo) - } ?: throw IllegalStateException("Platform signature not found") + private fun getSystemSignature(): String = getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo -> + getSignature(platformInfo) + } ?: throw IllegalStateException("Platform signature not found") /** * Creates a SHA-256 signature given a certificate byte array. diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt b/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt index c864e4a0d..f44d4f3e2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/BackupFileLocator.kt @@ -8,6 +8,7 @@ import java.io.File class BackupFileLocator(private val homeDir: File) { companion object { const val FILE_NAME_DB = "newpipe.db" + @Deprecated( "Serializing preferences with Java's ObjectOutputStream is vulnerable to injections", replaceWith = ReplaceWith("FILE_NAME_JSON_PREFS") diff --git a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt index 36e0b9ce1..83cca2e0b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt @@ -5,13 +5,13 @@ import com.grack.nanojson.JsonArray import com.grack.nanojson.JsonParser import com.grack.nanojson.JsonParserException import com.grack.nanojson.JsonWriter -import org.schabi.newpipe.streams.io.SharpOutputStream -import org.schabi.newpipe.streams.io.StoredFileHelper -import org.schabi.newpipe.util.ZipHelper import java.io.FileNotFoundException import java.io.IOException import java.io.ObjectOutputStream import java.util.zip.ZipOutputStream +import org.schabi.newpipe.streams.io.SharpOutputStream +import org.schabi.newpipe.streams.io.StoredFileHelper +import org.schabi.newpipe.util.ZipHelper class ImportExportManager(private val fileLocator: BackupFileLocator) { companion object { @@ -31,7 +31,7 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { ZipHelper.addFileToZip( outZip, BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path, + fileLocator.db.path ) // add the legacy vulnerable serialized preferences (will be removed in the future) @@ -78,7 +78,7 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { val success = ZipHelper.extractFileFromZip( file, BackupFileLocator.FILE_NAME_DB, - fileLocator.db.path, + fileLocator.db.path ) if (success) { @@ -122,10 +122,15 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { for ((key, value) in entries) { when (value) { is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is Set<*> -> { // There are currently only Sets with type String possible @Suppress("UNCHECKED_CAST") @@ -159,10 +164,15 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) { for ((key, value) in jsonObject) { when (value) { is Boolean -> editor.putBoolean(key, value) + is Float -> editor.putFloat(key, value) + is Int -> editor.putInt(key, value) + is Long -> editor.putLong(key, value) + is String -> editor.putString(key, value) + is JsonArray -> { editor.putStringSet(key, value.mapNotNull { e -> e as? String }.toSet()) } diff --git a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt index 581768c30..9dbfa826a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/notifications/NotificationModeConfigFragment.kt @@ -45,7 +45,7 @@ class NotificationModeConfigFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?, + savedInstanceState: Bundle? ): View { _binding = FragmentChannelsNotificationsBinding.inflate(inflater, container, false) return binding.root @@ -88,6 +88,7 @@ class NotificationModeConfigFragment : Fragment() { toggleAll() true } + else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.kt b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.kt index bfa50beef..1237a984b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.kt +++ b/app/src/main/java/org/schabi/newpipe/util/FilenameUtils.kt @@ -7,9 +7,9 @@ package org.schabi.newpipe.util import android.content.Context import androidx.preference.PreferenceManager +import java.util.regex.Matcher import org.schabi.newpipe.R import org.schabi.newpipe.ktx.getStringSafe -import java.util.regex.Matcher object FilenameUtils { private const val CHARSET_MOST_SPECIAL = "[\\n\\r|?*<\":\\\\>/']+" @@ -31,10 +31,12 @@ object FilenameUtils { val defaultCharset = context.getString(R.string.default_file_charset_value) val replacementChar = sharedPreferences.getStringSafe( - context.getString(R.string.settings_file_replacement_character_key), "_" + context.getString(R.string.settings_file_replacement_character_key), + "_" ) val selectedCharset = sharedPreferences.getStringSafe( - context.getString(R.string.settings_file_charset_key), "" + context.getString(R.string.settings_file_charset_key), + "" ).ifEmpty { defaultCharset } val charset = when (selectedCharset) { diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 3ea19fa4f..bc3849384 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -2,13 +2,13 @@ package org.schabi.newpipe.util import android.content.pm.PackageManager import androidx.core.content.pm.PackageInfoCompat +import java.time.Instant +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import org.schabi.newpipe.App import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification import org.schabi.newpipe.error.UserAction -import java.time.Instant -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter object ReleaseVersionUtil { // Public key of the certificate that is used in NewPipe release versions @@ -26,7 +26,8 @@ object ReleaseVersionUtil { PackageInfoCompat.hasSignatures(app.packageManager, app.packageName, certificates, false) } catch (e: PackageManager.NameNotFoundException) { createNotification( - app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info") + app, + ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info") ) false } diff --git a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt index aa59b4d0a..c7e94c7f8 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt +++ b/app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.kt @@ -5,9 +5,9 @@ package org.schabi.newpipe.util.image +import kotlin.math.abs import org.schabi.newpipe.extractor.Image import org.schabi.newpipe.extractor.Image.ResolutionLevel -import kotlin.math.abs object ImageStrategy { // when preferredImageQuality is LOW or MEDIUM, images are sorted by how close their preferred @@ -68,7 +68,7 @@ object ImageStrategy { val initialComparator = Comparator // the first step splits the images into groups of resolution levels .comparingInt { i: Image -> - return@comparingInt when (i.estimatedResolutionLevel) { + return@comparingInt when (i.estimatedResolutionLevel) { // avoid unknowns as much as possible ResolutionLevel.UNKNOWN -> 3 @@ -92,6 +92,7 @@ object ImageStrategy { // the same number for those. val finalComparator = when (nonNoneQuality) { PreferredImageQuality.NONE -> initialComparator + PreferredImageQuality.LOW -> initialComparator.thenComparingDouble { image -> val pixelCount = estimatePixelCount(image, widthOverHeight) abs(pixelCount - BEST_LOW_H * BEST_LOW_H * widthOverHeight) diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenException.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenException.kt index 879f8f3e6..683fd48b2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenException.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenException.kt @@ -6,8 +6,9 @@ class PoTokenException(message: String) : Exception(message) class BadWebViewException(message: String) : Exception(message) fun buildExceptionForJsError(error: String): Exception { - return if (error.contains("SyntaxError")) + return if (error.contains("SyntaxError")) { BadWebViewException(error) - else + } else { PoTokenException(error) + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt index 5383a613a..12fadb68d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenProviderImpl.kt @@ -37,7 +37,9 @@ object PoTokenProviderImpl : PoTokenProvider { webViewBadImpl = true return null } + null -> throw e + else -> throw cause // includes PoTokenException } } @@ -58,7 +60,6 @@ object PoTokenProviderImpl : PoTokenProvider { webPoTokenGenerator!!.isExpired() if (shouldRecreate) { - val innertubeClientRequestInfo = InnertubeClientRequestInfo.ofWebClient() innertubeClientRequestInfo.clientInfo.clientVersion = YoutubeParsingHelper.getClientVersion() diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt index 9b4b500f0..deeef613a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt +++ b/app/src/main/java/org/schabi/newpipe/util/potoken/PoTokenWebView.kt @@ -16,14 +16,14 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers +import java.time.Instant import org.schabi.newpipe.BuildConfig import org.schabi.newpipe.DownloaderImpl -import java.time.Instant class PoTokenWebView private constructor( context: Context, // to be used exactly once only during initialization! - private val generatorEmitter: SingleEmitter, + private val generatorEmitter: SingleEmitter ) : PoTokenGenerator { private val webView = WebView(context) private val disposables = CompositeDisposable() // used only during initialization @@ -93,7 +93,7 @@ class PoTokenWebView private constructor( ), "text/html", "utf-8", - null, + null ) }, this::onInitializationErrorCloseAndCancel @@ -113,7 +113,7 @@ class PoTokenWebView private constructor( makeBotguardServiceRequest( "https://www.youtube.com/api/jnn/v1/Create", - "[ \"$REQUEST_KEY\" ]", + "[ \"$REQUEST_KEY\" ]" ) { responseBody -> val parsedChallengeData = parseChallengeData(responseBody) webView.evaluateJavascript( @@ -156,7 +156,7 @@ class PoTokenWebView private constructor( } makeBotguardServiceRequest( "https://www.youtube.com/api/jnn/v1/GenerateIT", - "[ \"$REQUEST_KEY\", \"$botguardResponse\" ]", + "[ \"$REQUEST_KEY\", \"$botguardResponse\" ]" ) { responseBody -> if (BuildConfig.DEBUG) { Log.d(TAG, "GenerateIT response: $responseBody") @@ -179,16 +179,15 @@ class PoTokenWebView private constructor( //endregion //region Obtaining poTokens - override fun generatePoToken(identifier: String): Single = - Single.create { emitter -> - if (BuildConfig.DEBUG) { - Log.d(TAG, "generatePoToken() called with identifier $identifier") - } - runOnMainThread(emitter) { - addPoTokenEmitter(identifier, emitter) - val u8Identifier = stringToU8(identifier) - webView.evaluateJavascript( - """try { + override fun generatePoToken(identifier: String): Single = Single.create { emitter -> + if (BuildConfig.DEBUG) { + Log.d(TAG, "generatePoToken() called with identifier $identifier") + } + runOnMainThread(emitter) { + addPoTokenEmitter(identifier, emitter) + val u8Identifier = stringToU8(identifier) + webView.evaluateJavascript( + """try { identifier = "$identifier" u8Identifier = $u8Identifier poTokenU8 = obtainPoToken(webPoSignalOutput, integrityToken, u8Identifier) @@ -200,10 +199,10 @@ class PoTokenWebView private constructor( $JS_INTERFACE.onObtainPoTokenResult(identifier, poTokenU8String) } catch (error) { $JS_INTERFACE.onObtainPoTokenError(identifier, error + "\n" + error.stack) - }""", - ) {} - } + }""" + ) {} } + } /** * Called by the JavaScript snippet from [generatePoToken] when an error occurs in calling the @@ -245,6 +244,7 @@ class PoTokenWebView private constructor( //endregion //region Handling multiple emitters + /** * Adds the ([identifier], [emitter]) pair to the [poTokenEmitters] list. This makes it so that * multiple poToken requests can be generated invparallel, and the results will be notified to @@ -283,6 +283,7 @@ class PoTokenWebView private constructor( //endregion //region Utils + /** * Makes a POST request to [url] with the given [data] by setting the correct headers. Calls * [onInitializationErrorCloseAndCancel] in case of any network errors and also if the response @@ -294,7 +295,7 @@ class PoTokenWebView private constructor( private fun makeBotguardServiceRequest( url: String, data: String, - handleResponseBody: (String) -> Unit, + handleResponseBody: (String) -> Unit ) { disposables.add( Single.fromCallable { @@ -306,7 +307,7 @@ class PoTokenWebView private constructor( "Accept" to listOf("application/json"), "Content-Type" to listOf("application/json+protobuf"), "x-goog-api-key" to listOf(GOOGLE_API_KEY), - "x-user-agent" to listOf("grpc-web-javascript/0.1"), + "x-user-agent" to listOf("grpc-web-javascript/0.1") ), data.toByteArray() ) @@ -363,6 +364,7 @@ class PoTokenWebView private constructor( companion object : PoTokenGenerator.Factory { private val TAG = PoTokenWebView::class.simpleName + // Public API key used by BotGuard, which has been got by looking at BotGuard requests private const val GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw" // NOSONAR private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo" @@ -370,14 +372,13 @@ class PoTokenWebView private constructor( "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3" private const val JS_INTERFACE = "PoTokenWebView" - override fun newPoTokenGenerator(context: Context): Single = - Single.create { emitter -> - runOnMainThread(emitter) { - val potWv = PoTokenWebView(context, emitter) - potWv.loadHtmlAndObtainBotguard(context) - emitter.setDisposable(potWv.disposables) - } + override fun newPoTokenGenerator(context: Context): Single = Single.create { emitter -> + runOnMainThread(emitter) { + val potWv = PoTokenWebView(context, emitter) + potWv.loadHtmlAndObtainBotguard(context) + emitter.setDisposable(potWv.disposables) } + } /** * Runs [runnable] on the main thread using `Handler(Looper.getMainLooper()).post()`, and @@ -385,7 +386,7 @@ class PoTokenWebView private constructor( */ private fun runOnMainThread( emitterIfPostFails: SingleEmitter, - runnable: Runnable, + runnable: Runnable ) { if (!Handler(Looper.getMainLooper()).post(runnable)) { emitterIfPostFails.onError(PoTokenException("Could not run on main thread")) diff --git a/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt index a76c5c31a..c99e2f639 100644 --- a/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt +++ b/app/src/main/java/org/schabi/newpipe/util/text/TimestampLongPressClickableSpan.kt @@ -53,8 +53,10 @@ class TimestampLongPressClickableSpan( when (relatedInfoService) { ServiceList.YouTube -> return relatedStreamUrl + "&t=" + timestampMatchDTO.seconds() + ServiceList.SoundCloud, ServiceList.MediaCCC -> return relatedStreamUrl + "#t=" + timestampMatchDTO.seconds() + ServiceList.PeerTube -> return relatedStreamUrl + "?start=" + timestampMatchDTO.seconds() } diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index 877070a91..08b7df6af 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -52,8 +52,9 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : private var initTap: Boolean = false override fun onDoubleTapStarted(portion: DisplayPortion) { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onDoubleTapStarted called with portion = [$portion]") + } initTap = false @@ -64,7 +65,7 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : val shouldForward: Boolean = performListener?.getFastSeekDirection(portion)?.directionAsBoolean ?: return - if (DEBUG) + if (DEBUG) { Log.d( TAG, "onDoubleTapProgressDown called with " + @@ -72,6 +73,7 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : "wasForwarding = [$wasForwarding], " + "initTap = [$initTap], " ) + } /* * Check if a initial tap occurred or if direction was switched @@ -97,8 +99,9 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : } override fun onDoubleTapFinished() { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "onDoubleTapFinished called with initTap = [$initTap]") + } if (initTap) performListener?.onDoubleTapEnd() initTap = false @@ -112,8 +115,10 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : clone(rootConstraintLayout) clear(secondsView.id, if (forward) START else END) connect( - secondsView.id, if (forward) END else START, - PARENT_ID, if (forward) END else START + secondsView.id, + if (forward) END else START, + PARENT_ID, + if (forward) END else START ) secondsView.startAnimation() applyTo(rootConstraintLayout) @@ -123,6 +128,7 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : interface PerformListener { fun onDoubleTap() fun onDoubleTapEnd() + /** * Determines if the playback should forward/rewind or do nothing. */ @@ -132,7 +138,7 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : enum class FastSeekDirection(val directionAsBoolean: Boolean?) { NONE(null), FORWARD(true), - BACKWARD(false); + BACKWARD(false) } } diff --git a/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt b/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt index 8472653fb..5e4885129 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/SecondsView.kt @@ -29,7 +29,9 @@ class SecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context var seconds: Int = 0 set(value) { binding.tvSeconds.text = context.resources.getQuantityString( - R.plurals.seconds, value, value + R.plurals.seconds, + value, + value ) field = value } diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt index c2f9dc9b2..c145b8506 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt @@ -1,13 +1,13 @@ package us.shandian.giga.get import android.os.Parcelable +import java.io.Serializable import kotlinx.parcelize.Parcelize import org.schabi.newpipe.extractor.MediaFormat import org.schabi.newpipe.extractor.stream.AudioStream import org.schabi.newpipe.extractor.stream.Stream import org.schabi.newpipe.extractor.stream.SubtitlesStream import org.schabi.newpipe.extractor.stream.VideoStream -import java.io.Serializable @Parcelize class MissionRecoveryInfo( @@ -25,16 +25,19 @@ class MissionRecoveryInfo( isDesired2 = false kind = 'a' } + is VideoStream -> { desired = stream.getResolution() isDesired2 = stream.isVideoOnly() kind = 'v' } + is SubtitlesStream -> { desired = stream.languageTag isDesired2 = stream.isAutoGenerated kind = 's' } + else -> throw RuntimeException("Unknown stream kind") } } @@ -48,14 +51,17 @@ class MissionRecoveryInfo( str.append("audio") info = "bitrate=$desiredBitrate" } + 'v' -> { str.append("video") info = "quality=$desired videoOnly=$isDesired2" } + 's' -> { str.append("subtitles") info = "language=$desired autoGenerated=$isDesired2" } + else -> { info = "" str.append("other") diff --git a/app/src/test/java/org/schabi/newpipe/NewVersionManagerTest.kt b/app/src/test/java/org/schabi/newpipe/NewVersionManagerTest.kt index 7a2d965f7..f10d9553f 100644 --- a/app/src/test/java/org/schabi/newpipe/NewVersionManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/NewVersionManagerTest.kt @@ -1,14 +1,14 @@ package org.schabi.newpipe +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import kotlin.math.abs import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import kotlin.math.abs class NewVersionManagerTest { diff --git a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt index ca2d04c85..fb7c1afe5 100644 --- a/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt +++ b/app/src/test/java/org/schabi/newpipe/ktx/ThrowableExtensionsTest.kt @@ -1,12 +1,12 @@ package org.schabi.newpipe.ktx -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test import java.io.IOException import java.io.InterruptedIOException import java.net.SocketException import javax.net.ssl.SSLException +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test class ThrowableExtensionsTest { @Test fun `assignable causes`() { diff --git a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt index 2fc44d9b7..6db72255b 100644 --- a/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt +++ b/app/src/test/java/org/schabi/newpipe/local/subscription/FeedGroupIconTest.kt @@ -15,7 +15,8 @@ class FeedGroupIconTest { assertEquals( "Gap between ids detected (current item: ${currentIcon.name} - ${currentIcon.id} → should be: $shouldBeId)", - shouldBeId, currentIcon.id + shouldBeId, + currentIcon.id ) } } diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt index 862ac3b80..c7f53f3ac 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportAllCombinationsTest.kt @@ -1,6 +1,9 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences +import java.io.File +import java.io.IOException +import java.nio.file.Files import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -8,9 +11,6 @@ import org.schabi.newpipe.settings.export.BackupFileLocator import org.schabi.newpipe.settings.export.ImportExportManager import org.schabi.newpipe.streams.io.StoredFileHelper import us.shandian.giga.io.FileStream -import java.io.File -import java.io.IOException -import java.nio.file.Files class ImportAllCombinationsTest { @@ -21,7 +21,7 @@ class ImportAllCombinationsTest { private enum class Ser(val id: String) { YES("ser"), VULNERABLE("vulnser"), - NO("noser"); + NO("noser") } private data class FailData( @@ -29,7 +29,7 @@ class ImportAllCombinationsTest { val containsSer: Ser, val containsJson: Boolean, val filename: String, - val throwable: Throwable, + val throwable: Throwable ) private fun testZipCombination( @@ -37,7 +37,7 @@ class ImportAllCombinationsTest { containsSer: Ser, containsJson: Boolean, filename: String, - runTest: (test: () -> Unit) -> Unit, + runTest: (test: () -> Unit) -> Unit ) { val zipFile = File(classloader.getResource(filename)?.file!!) val zip = Mockito.mock(StoredFileHelper::class.java, Mockito.withSettings().stubOnly()) @@ -93,6 +93,7 @@ class ImportAllCombinationsTest { Mockito.verify(editor, Mockito.atLeastOnce()) .putInt(Mockito.anyString(), Mockito.anyInt()) } + Ser.VULNERABLE -> runTest { Assert.assertTrue(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) Assert.assertThrows(ClassNotFoundException::class.java) { @@ -102,6 +103,7 @@ class ImportAllCombinationsTest { Mockito.verify(editor, Mockito.never()).clear() Mockito.verify(editor, Mockito.never()).commit() } + Ser.NO -> runTest { Assert.assertFalse(ImportExportManager(fileLocator).exportHasSerializedPrefs(zip)) Assert.assertThrows(IOException::class.java) { @@ -152,15 +154,18 @@ class ImportAllCombinationsTest { for (containsSer in Ser.entries) { for (containsJson in listOf(true, false)) { val filename = "settings/${if (containsDb) "db" else "nodb"}_${ - containsSer.id}_${if (containsJson) "json" else "nojson"}.zip" + containsSer.id}_${if (containsJson) "json" else "nojson"}.zip" testZipCombination(containsDb, containsSer, containsJson, filename) { test -> try { test() } catch (e: Throwable) { failedAssertions.add( FailData( - containsDb, containsSer, containsJson, - filename, e + containsDb, + containsSer, + containsJson, + filename, + e ) ) } @@ -173,7 +178,7 @@ class ImportAllCombinationsTest { for (a in failedAssertions) { println( "Assertion failed with containsDb=${a.containsDb}, containsSer=${ - a.containsSer}, containsJson=${a.containsJson}, filename=${a.filename}:" + a.containsSer}, containsJson=${a.containsJson}, filename=${a.filename}:" ) a.throwable.printStackTrace() println() diff --git a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt index 5b8023561..e2ff22134 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -2,6 +2,10 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences import com.grack.nanojson.JsonParser +import java.io.File +import java.io.ObjectInputStream +import java.nio.file.Files +import java.util.zip.ZipFile import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows @@ -23,10 +27,6 @@ import org.schabi.newpipe.settings.export.BackupFileLocator import org.schabi.newpipe.settings.export.ImportExportManager import org.schabi.newpipe.streams.io.StoredFileHelper import us.shandian.giga.io.FileStream -import java.io.File -import java.io.ObjectInputStream -import java.nio.file.Files -import java.util.zip.ZipFile @RunWith(MockitoJUnitRunner::class) class ImportExportManagerTest { diff --git a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt index ab6396951..081d1c545 100644 --- a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt +++ b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt @@ -1,12 +1,12 @@ package org.schabi.newpipe.util -import org.junit.Assert.assertEquals -import org.junit.Test -import org.ocpsoft.prettytime.PrettyTime import java.time.LocalDate import java.time.OffsetDateTime import java.time.ZoneOffset import java.util.Locale +import org.junit.Assert.assertEquals +import org.junit.Test +import org.ocpsoft.prettytime.PrettyTime class LocalizationTest { @Test(expected = NullPointerException::class)