diff --git a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.kt b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.kt index 2dee463ef..85f866885 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.kt @@ -6,6 +6,8 @@ import android.os.Parcelable import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest +import java.io.IOException +import java.net.SocketTimeoutException import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull @@ -16,8 +18,6 @@ import org.schabi.newpipe.R import org.schabi.newpipe.extractor.ServiceList import org.schabi.newpipe.extractor.exceptions.ParsingException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException -import java.io.IOException -import java.net.SocketTimeoutException /** * Instrumented tests for {@link ErrorInfo}. diff --git a/app/src/androidTest/java/org/schabi/newpipe/ui/components/common/ErrorPanelTest.kt b/app/src/androidTest/java/org/schabi/newpipe/ui/components/common/ErrorPanelTest.kt index a6627ee66..8c96b3cdc 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/ui/components/common/ErrorPanelTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/ui/components/common/ErrorPanelTest.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 +import java.net.UnknownHostException import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -16,7 +17,6 @@ import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException import org.schabi.newpipe.ui.theme.AppTheme -import java.net.UnknownHostException @RunWith(AndroidJUnit4::class) class ErrorPanelTest { diff --git a/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionInstrumentedTest.kt b/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionInstrumentedTest.kt index eed80ead4..78088f4a2 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionInstrumentedTest.kt +++ b/app/src/androidTest/java/org/schabi/newpipe/ui/components/video/comment/CommentSectionInstrumentedTest.kt @@ -34,6 +34,7 @@ import androidx.paging.LoadState import androidx.paging.LoadStates import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems +import java.net.UnknownHostException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -54,7 +55,6 @@ import org.schabi.newpipe.ui.emptystate.EmptyStateComposable import org.schabi.newpipe.ui.emptystate.EmptyStateSpec import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.viewmodels.util.Resource -import java.net.UnknownHostException class CommentSectionInstrumentedTest { @@ -277,6 +277,7 @@ private fun TestCommentSection( is Resource.Loading -> item { LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) } + is Resource.Success -> { val commentInfo = (uiState as Resource.Success).data val count = commentInfo.commentCount @@ -290,6 +291,7 @@ private fun TestCommentSection( .heightIn(min = 128.dp) ) } + count == 0 -> item { EmptyStateComposable( spec = EmptyStateSpec.NoComments, @@ -298,6 +300,7 @@ private fun TestCommentSection( .heightIn(min = 128.dp) ) } + else -> { if (count >= 0) { item { @@ -314,6 +317,7 @@ private fun TestCommentSection( is LoadState.Loading -> item { LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) } + is LoadState.Error -> item { Box( modifier = Modifier.fillMaxWidth() @@ -329,6 +333,7 @@ private fun TestCommentSection( ) } } + else -> items(comments.itemCount) { index -> Comment(comment = comments[index]!!) {} } @@ -336,6 +341,7 @@ private fun TestCommentSection( } } } + is Resource.Error -> item { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/org/schabi/newpipe/App.kt b/app/src/main/java/org/schabi/newpipe/App.kt index 0505f272f..703f3e213 100644 --- a/app/src/main/java/org/schabi/newpipe/App.kt +++ b/app/src/main/java/org/schabi/newpipe/App.kt @@ -22,6 +22,9 @@ import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException import io.reactivex.rxjava3.exceptions.UndeliverableException import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.plugins.RxJavaPlugins +import java.io.IOException +import java.io.InterruptedIOException +import java.net.SocketException import org.acra.ACRA.init import org.acra.ACRA.isACRASenderServiceProcess import org.acra.config.CoreConfigurationBuilder @@ -38,9 +41,6 @@ import org.schabi.newpipe.util.StateSaver import org.schabi.newpipe.util.image.ImageStrategy import org.schabi.newpipe.util.image.PreferredImageQuality import org.schabi.newpipe.util.potoken.PoTokenProviderImpl -import java.io.IOException -import java.io.InterruptedIOException -import java.net.SocketException /* * Copyright (C) Hans-Christoph Steiner 2016 @@ -101,7 +101,7 @@ open class App : NewPipe.init( getDownloader(), Localization.getPreferredLocalization(this), - Localization.getPreferredContentCountry(this), + Localization.getPreferredContentCountry(this) ) Localization.initPrettyTime(Localization.resolvePrettyTime()) @@ -118,9 +118,9 @@ open class App : this, prefs.getString( getString(R.string.image_quality_key), - getString(R.string.image_quality_default), - ), - ), + getString(R.string.image_quality_default) + ) + ) ) configureRxJavaErrorHandler() @@ -128,15 +128,14 @@ open class App : YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl) } - override fun newImageLoader(context: Context): ImageLoader = - ImageLoader - .Builder(this) - .logger(if (BuildConfig.DEBUG) DebugLogger() else null) - .allowRgb565(getSystemService()!!.isLowRamDevice) - .crossfade(true) - .components { - add(OkHttpNetworkFetcherFactory(callFactory = DownloaderImpl.getInstance().client)) - }.build() + override fun newImageLoader(context: Context): ImageLoader = ImageLoader + .Builder(this) + .logger(if (BuildConfig.DEBUG) DebugLogger() else null) + .allowRgb565(getSystemService()!!.isLowRamDevice) + .crossfade(true) + .components { + add(OkHttpNetworkFetcherFactory(callFactory = DownloaderImpl.getInstance().client)) + }.build() protected open fun getDownloader(): Downloader { val downloader = DownloaderImpl.init(null) @@ -190,7 +189,7 @@ open class App : IOException::class.java, SocketException::class.java, // blocking code disposed InterruptedException::class.java, - InterruptedIOException::class.java, + InterruptedIOException::class.java ) } @@ -204,7 +203,7 @@ open class App : OnErrorNotImplementedException::class.java, MissingBackpressureException::class.java, // bug in operator - IllegalStateException::class.java, + IllegalStateException::class.java ) } @@ -215,7 +214,7 @@ open class App : .uncaughtExceptionHandler .uncaughtException(Thread.currentThread(), throwable) } - }, + } ) } @@ -241,7 +240,7 @@ open class App : NotificationChannelCompat .Builder( getString(R.string.notification_channel_id), - NotificationManagerCompat.IMPORTANCE_LOW, + NotificationManagerCompat.IMPORTANCE_LOW ).setName(getString(R.string.notification_channel_name)) .setDescription(getString(R.string.notification_channel_description)) .build() @@ -249,7 +248,7 @@ open class App : NotificationChannelCompat .Builder( getString(R.string.app_update_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_LOW, + NotificationManagerCompat.IMPORTANCE_LOW ).setName(getString(R.string.app_update_notification_channel_name)) .setDescription(getString(R.string.app_update_notification_channel_description)) .build() @@ -257,7 +256,7 @@ open class App : NotificationChannelCompat .Builder( getString(R.string.hash_channel_id), - NotificationManagerCompat.IMPORTANCE_HIGH, + NotificationManagerCompat.IMPORTANCE_HIGH ).setName(getString(R.string.hash_channel_name)) .setDescription(getString(R.string.hash_channel_description)) .build() @@ -265,7 +264,7 @@ open class App : NotificationChannelCompat .Builder( getString(R.string.error_report_channel_id), - NotificationManagerCompat.IMPORTANCE_LOW, + NotificationManagerCompat.IMPORTANCE_LOW ).setName(getString(R.string.error_report_channel_name)) .setDescription(getString(R.string.error_report_channel_description)) .build() @@ -273,7 +272,7 @@ open class App : NotificationChannelCompat .Builder( getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT, + NotificationManagerCompat.IMPORTANCE_DEFAULT ).setName(getString(R.string.streams_notification_channel_name)) .setDescription(getString(R.string.streams_notification_channel_description)) .build() 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 45ab8daa0..e86539fa4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -6,6 +6,7 @@ import androidx.annotation.StringRes 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.ktx.isNetworkRelated import org.schabi.newpipe.player.mediasource.FailedMediaSource import org.schabi.newpipe.player.resolver.PlaybackResolver import org.schabi.newpipe.util.Localization -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 { // use Localization.compatGetString() just in case context is not AppCompatActivity @@ -158,21 +158,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 @@ -191,18 +189,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) @@ -218,34 +222,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 -> @@ -254,16 +270,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) } @@ -274,18 +296,23 @@ 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 + // a recaptcha was detected, and the user needs to solve it, there is no use in // letting users report it is ReCaptchaException -> false + // 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 } @@ -295,8 +322,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/fragments/detail/VideoDetailFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt index d1514fbe5..e4556f512 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt @@ -56,6 +56,11 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers +import java.util.LinkedList +import java.util.concurrent.TimeUnit +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.database.stream.model.StreamEntity @@ -115,11 +120,6 @@ import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.KoreUtils import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.image.CoilHelper -import java.util.LinkedList -import java.util.concurrent.TimeUnit -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min class VideoDetailFragment : BaseStateFragment(), @@ -128,15 +128,29 @@ class VideoDetailFragment : OnKeyDownListener { // stream info - @JvmField @State var serviceId: Int = NO_SERVICE_ID - @JvmField @State var title: String = "" - @JvmField @State var url: String? = null + @JvmField + @State + var serviceId: Int = NO_SERVICE_ID + + @JvmField + @State + var title: String = "" + + @JvmField + @State + var url: String? = null private var currentInfo: StreamInfo? = null // player objects private var playQueue: PlayQueue? = null - @JvmField @State var autoPlayEnabled: Boolean = true - @JvmField @State var originalOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + + @JvmField + @State + var autoPlayEnabled: Boolean = true + + @JvmField + @State + var originalOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED private var playerService: PlayerService? = null private var player: Player? = null @@ -152,7 +166,9 @@ class VideoDetailFragment : private var showRelatedItems = false private var showDescription = false private lateinit var selectedTabTag: String + @AttrRes val tabIcons = ArrayList() + @StringRes val tabContentDescriptions = ArrayList() private var tabSettingsChanged = false private var lastAppBarVerticalOffset = Int.Companion.MAX_VALUE // prevents useless updates @@ -172,8 +188,13 @@ class VideoDetailFragment : } // bottom sheet - @JvmField @State var bottomSheetState: Int = BottomSheetBehavior.STATE_EXPANDED - @JvmField @State var lastStableBottomSheetState: Int = BottomSheetBehavior.STATE_EXPANDED + @JvmField + @State + var bottomSheetState: Int = BottomSheetBehavior.STATE_EXPANDED + + @JvmField + @State + var lastStableBottomSheetState: Int = BottomSheetBehavior.STATE_EXPANDED private lateinit var bottomSheetBehavior: BottomSheetBehavior private lateinit var bottomSheetCallback: BottomSheetCallback private lateinit var broadcastReceiver: BroadcastReceiver @@ -244,7 +265,8 @@ class VideoDetailFragment : showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true) showDescription = prefs.getBoolean(getString(R.string.show_description_key), true) selectedTabTag = prefs.getString( - getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG + getString(R.string.stream_info_selected_tab_key), + COMMENTS_TAB_TAG )!! prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener) @@ -258,7 +280,8 @@ class VideoDetailFragment : } } activity.contentResolver.registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), + false, settingsContentObserver!! ) } @@ -357,7 +380,13 @@ class VideoDetailFragment : if (requestCode == ReCaptchaActivity.RECAPTCHA_REQUEST) { if (resultCode == Activity.RESULT_OK) { NavigationHelper.openVideoDetailFragment( - requireContext(), getFM(), serviceId, url, title, null, false + requireContext(), + getFM(), + serviceId, + url, + title, + null, + false ) } else { Log.e(TAG, "ReCaptcha failed") @@ -562,7 +591,7 @@ class VideoDetailFragment : KoreUtils.shouldShowPlayWithKodi(requireContext(), serviceId) binding.detailControlsCrashThePlayer.isVisible = DEBUG && PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.show_crash_the_player_key), false) + .getBoolean(getString(R.string.show_crash_the_player_key), false) accommodateForTvAndDesktopMode() } @@ -845,7 +874,9 @@ class VideoDetailFragment : private fun updateTabs(info: StreamInfo) { if (showRelatedItems) { when (val relatedItemsLayout = binding.relatedItemsLayout) { - null -> pageAdapter.updateItem(RELATED_TAB_TAG, getInstance(info)) // phone + null -> pageAdapter.updateItem(RELATED_TAB_TAG, getInstance(info)) + + // phone else -> { // tablet + TV getChildFragmentManager().beginTransaction() .replace(R.id.relatedItemsLayout, getInstance(info)) @@ -896,7 +927,9 @@ class VideoDetailFragment : val viewPagerVisibleHeight = height - pagerHitRect.top // see TabLayout.DEFAULT_HEIGHT, which is equal to 48dp val tabLayoutHeight = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 48f, resources.displayMetrics + TypedValue.COMPLEX_UNIT_DIP, + 48f, + resources.displayMetrics ) if (viewPagerVisibleHeight > tabLayoutHeight * 2) { @@ -997,7 +1030,7 @@ class VideoDetailFragment : } if (PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(this.getString(R.string.use_external_video_player_key), false) + .getBoolean(this.getString(R.string.use_external_video_player_key), false) ) { showExternalVideoPlaybackDialog() } else { @@ -1046,7 +1079,10 @@ class VideoDetailFragment : tryAddVideoPlayerView() val playerIntent = NavigationHelper.getPlayerIntent( - requireContext(), PlayerService::class.java, queue, PlayerIntentType.AllOthers + requireContext(), + PlayerService::class.java, + queue, + PlayerIntentType.AllOthers ) .putExtra(Player.PLAY_WHEN_READY, autoPlayEnabled) .putExtra(Player.RESUME_PLAYBACK, true) @@ -1102,7 +1138,10 @@ class VideoDetailFragment : selectedStream: Stream ) { NavigationHelper.playOnExternalPlayer( - context, info.name, info.subChannelName, selectedStream + context, + info.name, + info.subChannelName, + selectedStream ) val recordManager = HistoryRecordManager(requireContext()) @@ -1173,10 +1212,11 @@ class VideoDetailFragment : private val preDrawListener: OnPreDrawListener = OnPreDrawListener { view?.let { view -> - val decorView = if (DeviceUtils.isInMultiWindow(activity)) + val decorView = if (DeviceUtils.isInMultiWindow(activity)) { view - else + } else { activity.window.decorView + } setHeightThumbnail(decorView.height, resources.displayMetrics) view.getViewTreeObserver().removeOnPreDrawListener(preDrawListener) } @@ -1196,10 +1236,11 @@ class VideoDetailFragment : if (this.isFullscreen) { val height = ( - if (DeviceUtils.isInMultiWindow(activity)) + if (DeviceUtils.isInMultiWindow(activity)) { requireView() - else + } else { activity.window.decorView + } ).height // Height is zero when the view is not yet displayed like after orientation change if (height != 0) { @@ -1210,10 +1251,11 @@ class VideoDetailFragment : } else { val isPortrait = metrics.heightPixels > metrics.widthPixels val height = ( - if (isPortrait) + if (isPortrait) { metrics.widthPixels / (16.0f / 9.0f) - else + } else { metrics.heightPixels / 2.0f + } ).toInt() setHeightThumbnail(height, metrics) } @@ -1288,7 +1330,9 @@ class VideoDetailFragment : override fun onReceive(context: Context?, intent: Intent) { when (intent.action) { ACTION_SHOW_MAIN_PLAYER -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED) + ACTION_HIDE_MAIN_PLAYER -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN) + ACTION_PLAYER_STARTED -> { // If the state is not hidden we don't need to show the mini player if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { @@ -1446,8 +1490,10 @@ class VideoDetailFragment : checkUpdateProgressInfo(info) CoilHelper.loadDetailsThumbnail(binding.detailThumbnailImageView, info.thumbnails) ExtractorHelper.showMetaInfoInTextView( - info.metaInfo, binding.detailMetaInfoTextView, - binding.detailMetaInfoSeparator, disposables + info.metaInfo, + binding.detailMetaInfoTextView, + binding.detailMetaInfoSeparator, + disposables ) if (playerIsStopped) { @@ -1561,7 +1607,9 @@ class VideoDetailFragment : .observeOn(AndroidSchedulers.mainThread()) .subscribe( { state -> updatePlaybackProgress(state.progressMillis, info.duration * 1000) }, - { throwable -> /* impossible due to the onErrorComplete() */ }, + { throwable -> + /* impossible due to the onErrorComplete() */ + }, { /* onComplete */ binding.positionView.visibility = View.GONE @@ -1607,7 +1655,7 @@ class VideoDetailFragment : Log.d( TAG, "onQueueUpdate() called with: serviceId = [$serviceId], url = [${ - url}], name = [$title], playQueue = [$playQueue]" + url}], name = [$title], playQueue = [$playQueue]" ) } @@ -1787,7 +1835,8 @@ class VideoDetailFragment : activity.window.decorView.systemUiVisibility = 0 activity.window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) activity.window.statusBarColor = ThemeHelper.resolveColorFromAttr( - requireContext(), android.R.attr.colorPrimary + requireContext(), + android.R.attr.colorPrimary ) } @@ -2013,7 +2062,8 @@ class VideoDetailFragment : if (audioTracks.isEmpty()) { Toast.makeText( - activity, R.string.no_audio_streams_available_for_external_players, + activity, + R.string.no_audio_streams_available_for_external_players, Toast.LENGTH_SHORT ).show() } else if (audioTracks.size == 1) { @@ -2053,6 +2103,7 @@ class VideoDetailFragment : /*////////////////////////////////////////////////////////////////////////// // Bottom mini player ////////////////////////////////////////////////////////////////////////// */ + /** * That's for Android TV support. Move focus from main fragment to the player or back * based on what is currently selected @@ -2311,6 +2362,7 @@ class VideoDetailFragment : /*////////////////////////////////////////////////////////////////////////// // OwnStack ////////////////////////////////////////////////////////////////////////// */ + /** * Stack that contains the "navigation history".

* The peek is the current video. 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 83e0c4082..7276051e8 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 @@ -37,7 +37,10 @@ class StreamSegmentItem( viewBinding.textViewStartSeconds.text = Localization.getDurationString(item.startTimeSeconds.toLong()) viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } - viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true } + viewBinding.root.setOnLongClickListener { + onClick.onItemLongClick(this, item.startTimeSeconds) + true + } viewBinding.root.isSelected = isSelected } diff --git a/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt b/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt index 4a3c3e071..140351b0d 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt @@ -9,5 +9,5 @@ inline fun Bitmap.scale( width: Int, height: Int, srcRect: Rect? = null, - scaleInLinearSpace: Boolean = true, + scaleInLinearSpace: Boolean = true ) = BitmapCompat.createScaledBitmap(this, width, height, srcRect, scaleInLinearSpace) 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 030bb7a76..258a67a4c 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.CoilHelper -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/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 74cbd63c9..045148844 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 @@ -231,7 +231,8 @@ class SubscriptionFragment : BaseStateFragment() { val data = result.data?.dataString if (data != null && result.resultCode == Activity.RESULT_OK) { ImportConfirmationDialog.show( - this, SubscriptionImportInput.PreviousExportMode(data) + this, + SubscriptionImportInput.PreviousExportMode(data) ) } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/ImportExportJsonHelper.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/ImportExportJsonHelper.kt index d71f5fa89..b95bcd508 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/ImportExportJsonHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/ImportExportJsonHelper.kt @@ -19,13 +19,13 @@ package org.schabi.newpipe.local.subscription.workers +import java.io.InputStream +import java.io.OutputStream import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.encodeToStream import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.InvalidSourceException -import java.io.InputStream -import java.io.OutputStream /** * A JSON implementation capable of importing and exporting subscriptions, it has the advantage @@ -65,7 +65,7 @@ object ImportExportJsonHelper { @JvmStatic fun writeTo( items: List, - out: OutputStream, + out: OutputStream ) { json.encodeToStream(SubscriptionData(items), out) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionExportWorker.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionExportWorker.kt index aa8000aa2..09e99aa6f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionExportWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionExportWorker.kt @@ -25,7 +25,7 @@ import org.schabi.newpipe.R class SubscriptionExportWorker( appContext: Context, - params: WorkerParameters, + params: WorkerParameters ) : CoroutineWorker(appContext, params) { // This is needed for API levels < 31 (Android S). override suspend fun getForegroundInfo(): ForegroundInfo { @@ -102,7 +102,7 @@ class SubscriptionExportWorker( fun schedule( context: Context, - uri: Uri, + uri: Uri ) { val data = workDataOf(EXPORT_PATH to uri.toString()) val workRequest = diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionImportWorker.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionImportWorker.kt index 86b9c739a..cc8cf6f24 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionImportWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/workers/SubscriptionImportWorker.kt @@ -31,7 +31,7 @@ import org.schabi.newpipe.util.ExtractorHelper class SubscriptionImportWorker( appContext: Context, - params: WorkerParameters, + params: WorkerParameters ) : CoroutineWorker(appContext, params) { // This is needed for API levels < 31 (Android S). override suspend fun getForegroundInfo(): ForegroundInfo { @@ -139,7 +139,7 @@ class SubscriptionImportWorker( title: String, text: String?, currentProgress: Int, - maxProgress: Int, + maxProgress: Int ): ForegroundInfo { val notification = NotificationCompat @@ -154,7 +154,7 @@ class SubscriptionImportWorker( .addAction( R.drawable.ic_close, applicationContext.getString(R.string.cancel), - WorkManager.getInstance(applicationContext).createCancelPendingIntent(id), + WorkManager.getInstance(applicationContext).createCancelPendingIntent(id) ).apply { if (currentProgress > 0 && maxProgress > 0) { val progressText = "$currentProgress/$maxProgress" @@ -187,8 +187,10 @@ class SubscriptionImportWorker( sealed class SubscriptionImportInput : Parcelable { @Parcelize data class ChannelUrlMode(val serviceId: Int, val url: String) : SubscriptionImportInput() + @Parcelize data class InputStreamMode(val serviceId: Int, val url: String) : SubscriptionImportInput() + @Parcelize data class PreviousExportMode(val url: String) : SubscriptionImportInput() @@ -218,6 +220,7 @@ sealed class SubscriptionImportInput : Parcelable { val url = data.getString("url")!! return ChannelUrlMode(serviceId, url) } + INPUT_STREAM_MODE -> { val serviceId = data.getInt("service_id", -1) if (serviceId == -1) { @@ -226,10 +229,12 @@ sealed class SubscriptionImportInput : Parcelable { val url = data.getString("url")!! return InputStreamMode(serviceId, url) } + PREVIOUS_EXPORT_MODE -> { val url = data.getString("url")!! return PreviousExportMode(url) } + else -> throw IllegalArgumentException("Unknown mode: $mode") } } diff --git a/app/src/main/java/org/schabi/newpipe/paging/CommentRepliesSource.kt b/app/src/main/java/org/schabi/newpipe/paging/CommentRepliesSource.kt index efa189db9..37eddceb1 100644 --- a/app/src/main/java/org/schabi/newpipe/paging/CommentRepliesSource.kt +++ b/app/src/main/java/org/schabi/newpipe/paging/CommentRepliesSource.kt @@ -10,7 +10,7 @@ import org.schabi.newpipe.extractor.comments.CommentsInfo import org.schabi.newpipe.extractor.comments.CommentsInfoItem class CommentRepliesSource( - private val commentInfo: CommentsInfoItem, + private val commentInfo: CommentsInfoItem ) : PagingSource() { private val service = NewPipe.getService(commentInfo.serviceId) diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt b/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt index abcc50b7a..1b9f2f57b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt @@ -29,6 +29,8 @@ import android.util.Log import androidx.core.app.ServiceCompat import androidx.media.MediaBrowserServiceCompat import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +import java.lang.ref.WeakReference +import java.util.function.Consumer import org.schabi.newpipe.ktx.toDebugString import org.schabi.newpipe.player.mediabrowser.MediaBrowserImpl import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer @@ -36,8 +38,6 @@ import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi import org.schabi.newpipe.player.notification.NotificationPlayerUi import org.schabi.newpipe.player.notification.NotificationUtil import org.schabi.newpipe.util.ThemeHelper -import java.lang.ref.WeakReference -import java.util.function.Consumer /** * One service for all players. @@ -110,7 +110,7 @@ class PlayerService : MediaBrowserServiceCompat() { Log.d( TAG, "onStartCommand() called with: intent = [$intent], extras = [${ - intent.extras.toDebugString()}], flags = [$flags], startId = [$startId]" + intent.extras.toDebugString()}], flags = [$flags], startId = [$startId]" ) } @@ -251,7 +251,7 @@ class PlayerService : MediaBrowserServiceCompat() { Log.d( TAG, "onBind() called with: intent = [$intent], extras = [${ - intent.extras.toDebugString()}]" + intent.extras.toDebugString()}]" ) } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.kt b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.kt index 67086c263..433214b80 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.kt +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.kt @@ -43,13 +43,13 @@ object PlayerHolder { private val playQueue: PlayQueue? get() = this.player?.playQueue + /** + * Returns the current [PlayerType] of the [PlayerService] service, + * otherwise `null` if no service is running. + * + * @return Current PlayerType + */ val type: PlayerType? - /** - * Returns the current [PlayerType] of the [PlayerService] service, - * otherwise `null` if no service is running. - * - * @return Current PlayerType - */ get() = this.player?.playerType val isPlaying: Boolean @@ -58,12 +58,12 @@ object PlayerHolder { val isPlayerOpen: Boolean get() = this.player != null + /** + * Use this method to only allow the user to manipulate the play queue (e.g. by enqueueing via + * the stream long press menu) when there actually is a play queue to manipulate. + * @return true only if the player is open and its play queue is ready (i.e. it is not null) + */ val isPlayQueueReady: Boolean - /** - * Use this method to only allow the user to manipulate the play queue (e.g. by enqueueing via - * the stream long press menu) when there actually is a play queue to manipulate. - * @return true only if the player is open and its play queue is ready (i.e. it is not null) - */ get() = this.playQueue != null val queueSize: Int 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 1aa12018c..4bf071c4d 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 @@ -47,7 +47,7 @@ import org.schabi.newpipe.util.image.ImageStrategy */ class MediaBrowserImpl( private val context: Context, - notifyChildrenChanged: (parentId: String) -> Unit, + notifyChildrenChanged: (parentId: String) -> Unit ) { private val packageValidator = PackageValidator(context) private val database = NewPipeDatabase.getInstance(context) @@ -87,7 +87,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) } @@ -135,7 +136,7 @@ class MediaBrowserImpl( ) } - when (/*val uriType = */path.removeAt(0)) { + when (path.removeAt(0)) { ID_BOOKMARKS -> { if (path.isEmpty()) { return populateBookmarks() @@ -206,7 +207,7 @@ class MediaBrowserImpl( return MediaBrowserCompat.MediaItem( builder.build(), - MediaBrowserCompat.MediaItem.FLAG_BROWSABLE, + MediaBrowserCompat.MediaItem.FLAG_BROWSABLE ) } @@ -260,7 +261,7 @@ class MediaBrowserImpl( private fun createLocalPlaylistStreamMediaItem( playlistId: Long, item: PlaylistStreamEntry, - index: Int, + index: Int ): MediaBrowserCompat.MediaItem { val builder = MediaDescriptionCompat.Builder() .setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index)) @@ -277,7 +278,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)) @@ -294,7 +295,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/playqueue/PlayQueue.kt b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt index 1fcaf1193..22b7e0022 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt @@ -4,6 +4,9 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.subjects.PublishSubject +import java.io.Serializable +import java.util.Collections +import java.util.concurrent.atomic.AtomicInteger import org.schabi.newpipe.player.playqueue.PlayQueueEvent.AppendEvent import org.schabi.newpipe.player.playqueue.PlayQueueEvent.ErrorEvent import org.schabi.newpipe.player.playqueue.PlayQueueEvent.InitEvent @@ -12,9 +15,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueueEvent.RecoveryEvent import org.schabi.newpipe.player.playqueue.PlayQueueEvent.RemoveEvent import org.schabi.newpipe.player.playqueue.PlayQueueEvent.ReorderEvent import org.schabi.newpipe.player.playqueue.PlayQueueEvent.SelectEvent -import java.io.Serializable -import java.util.Collections -import java.util.concurrent.atomic.AtomicInteger /** * PlayQueue is responsible for keeping track of a list of streams and the index of @@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger */ abstract class PlayQueue internal constructor( index: Int, - startWith: List, + startWith: List ) : Serializable { private val queueIndex = AtomicInteger(index) private val history = mutableListOf() @@ -105,22 +105,20 @@ abstract class PlayQueue internal constructor( /*////////////////////////////////////////////////////////////////////////// // Readonly ops ////////////////////////////////////////////////////////////////////////// */ + + /** + * Changes the current playing index to a new index. + * + * This method is guarded using in a circular manner for index exceeding the play queue size. + * + * Will emit a [SelectEvent] if the index is not the current playing index. + * + * @param index the index to be set + * @return the current index that should be played + */ @set:Synchronized var index: Int = 0 - /** - * @return the current index that should be played - */ get() = queueIndex.get() - - /** - * Changes the current playing index to a new index. - * - * This method is guarded using in a circular manner for index exceeding the play queue size. - * - * Will emit a [SelectEvent] if the index is not the current playing index. - * - * @param index the index to be set - */ set(index) { val oldIndex = field @@ -340,7 +338,7 @@ abstract class PlayQueue internal constructor( @Synchronized fun move( source: Int, - target: Int, + target: Int ) { if (source < 0 || target < 0) { return @@ -375,7 +373,7 @@ abstract class PlayQueue internal constructor( @Synchronized fun setRecovery( index: Int, - position: Long, + position: Long ) { streams.getOrNull(index)?.let { it.recoveryPosition = position diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.kt b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.kt index d6b4b0402..8ff91d4d9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.kt +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.kt @@ -2,13 +2,13 @@ package org.schabi.newpipe.player.playqueue import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers +import java.io.Serializable +import java.util.Objects import org.schabi.newpipe.extractor.Image import org.schabi.newpipe.extractor.stream.StreamInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.util.ExtractorHelper -import java.io.Serializable -import java.util.Objects class PlayQueueItem private constructor( val title: String, @@ -18,7 +18,7 @@ class PlayQueueItem private constructor( val thumbnails: List, val uploader: String, val uploaderUrl: String?, - val streamType: StreamType, + val streamType: StreamType ) : Serializable { // // ////////////////////////////////////////////////////////////////////// */ @@ -40,7 +40,7 @@ class PlayQueueItem private constructor( info.thumbnails, info.uploaderName.orEmpty(), info.uploaderUrl, - info.streamType, + info.streamType ) { if (info.startPosition > 0) { this.recoveryPosition = info.startPosition * 1000 @@ -55,7 +55,7 @@ class PlayQueueItem private constructor( item.thumbnails, item.uploaderName.orEmpty(), item.uploaderUrl, - item.streamType, + item.streamType ) val stream: Single diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt index 5419027a5..9db5a24f6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt @@ -1,8 +1,8 @@ package org.schabi.newpipe.player.ui -import org.schabi.newpipe.util.GuardedByMutex import kotlin.reflect.KClass import kotlin.reflect.safeCast +import org.schabi.newpipe.util.GuardedByMutex /** * Creates a [PlayerUiList] starting with the provided player uis. The provided player uis @@ -78,22 +78,20 @@ class PlayerUiList(vararg initialPlayerUis: PlayerUi) { * @param T the class type parameter * @return the first player UI of the required type found in the list, or null */ - fun get(playerUiType: KClass): T? = - playerUis.runWithLockSync { - for (ui in lockData) { - if (playerUiType.isInstance(ui)) { - // try all UIs before returning null - playerUiType.safeCast(ui)?.let { return@runWithLockSync it } - } + fun get(playerUiType: KClass): T? = playerUis.runWithLockSync { + for (ui in lockData) { + if (playerUiType.isInstance(ui)) { + // try all UIs before returning null + playerUiType.safeCast(ui)?.let { return@runWithLockSync it } } - return@runWithLockSync null } + return@runWithLockSync null + } /** * See [get] above */ - fun get(playerUiType: Class): T? = - get(playerUiType.kotlin) + fun get(playerUiType: Class): T? = get(playerUiType.kotlin) /** * Calls the provided consumer on all player UIs in the list, in order of addition. diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt index ac08dd36b..45d85866e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt @@ -13,7 +13,6 @@ import org.schabi.newpipe.ui.theme.SizeTokens @Composable fun DebugScreen(viewModel: SettingsViewModel, modifier: Modifier = Modifier) { - val settingsLayoutRedesign by viewModel.settingsLayoutRedesign.collectAsState() Column(modifier = modifier) { 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 cbf860d2c..b5ab72f51 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,15 +5,15 @@ 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 kotlin.io.path.createParentDirectories import kotlin.io.path.deleteIfExists +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 { @@ -117,10 +117,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") @@ -154,10 +159,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/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt index 1e48fef5e..7453096d6 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt @@ -6,11 +6,11 @@ import android.content.SharedPreferences import androidx.lifecycle.AndroidViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import org.schabi.newpipe.R import org.schabi.newpipe.util.Localization -import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( @@ -18,7 +18,7 @@ class SettingsViewModel @Inject constructor( private val preferenceManager: SharedPreferences ) : AndroidViewModel(context.applicationContext as Application) { - private var _settingsLayoutRedesignPref: Boolean + private var settingsLayoutRedesignPref: Boolean get() = preferenceManager.getBoolean( Localization.compatGetString(getApplication(), R.string.settings_layout_redesign_key), false @@ -30,11 +30,11 @@ class SettingsViewModel @Inject constructor( ).apply() } private val _settingsLayoutRedesign: MutableStateFlow = - MutableStateFlow(_settingsLayoutRedesignPref) + MutableStateFlow(settingsLayoutRedesignPref) val settingsLayoutRedesign = _settingsLayoutRedesign.asStateFlow() fun toggleSettingsLayoutRedesign(newState: Boolean) { _settingsLayoutRedesign.value = newState - _settingsLayoutRedesignPref = newState + settingsLayoutRedesignPref = newState } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt b/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt index d479343f5..37e46e34f 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt @@ -36,14 +36,14 @@ fun SwitchPreference( text = stringResource(id = title), modifier = Modifier.padding(SizeTokens.SpacingExtraSmall), style = MaterialTheme.typography.titleSmall, - textAlign = TextAlign.Start, + textAlign = TextAlign.Start ) summary?.let { Text( text = stringResource(id = summary), modifier = Modifier.padding(SizeTokens.SpacingExtraSmall), style = MaterialTheme.typography.bodySmall, - textAlign = TextAlign.Start, + textAlign = TextAlign.Start ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt b/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt index f58f2f305..2fad42d4d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt @@ -28,7 +28,7 @@ fun TextPreference( @StringRes title: Int, @DrawableRes icon: Int? = null, @StringRes summary: Int? = null, - onClick: () -> Unit, + onClick: () -> Unit ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -51,14 +51,14 @@ fun TextPreference( text = stringResource(id = title), modifier = Modifier.padding(SizeTokens.SpacingExtraSmall), style = MaterialTheme.typography.titleSmall, - textAlign = TextAlign.Start, + textAlign = TextAlign.Start ) summary?.let { Text( text = stringResource(id = summary), modifier = Modifier.padding(SizeTokens.SpacingExtraSmall), style = MaterialTheme.typography.bodySmall, - textAlign = TextAlign.Start, + textAlign = TextAlign.Start ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt b/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt index de0c97540..d3a20bb02 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt @@ -37,7 +37,8 @@ fun TextAction(text: String, modifier: Modifier = Modifier) { @Composable fun NavigationIcon() { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", modifier = Modifier.padding(horizontal = SizeTokens.SpacingExtraSmall) ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/AboutTab.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/AboutTab.kt index dbb10b9a8..c9c62cdff 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/AboutTab.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/AboutTab.kt @@ -37,20 +37,28 @@ import org.schabi.newpipe.util.image.NewPipeSquircleIcon private val ABOUT_ITEMS = listOf( AboutData(R.string.faq_title, R.string.faq_description, R.string.faq, R.string.faq_url), AboutData( - R.string.contribution_title, R.string.contribution_encouragement, - R.string.view_on_github, R.string.github_url + R.string.contribution_title, + R.string.contribution_encouragement, + R.string.view_on_github, + R.string.github_url ), AboutData( - R.string.donation_title, R.string.donation_encouragement, R.string.give_back, + R.string.donation_title, + R.string.donation_encouragement, + R.string.give_back, R.string.donation_url ), AboutData( - R.string.website_title, R.string.website_encouragement, R.string.open_in_browser, + R.string.website_title, + R.string.website_encouragement, + R.string.open_in_browser, R.string.website_url ), AboutData( - R.string.privacy_policy_title, R.string.privacy_policy_encouragement, - R.string.read_privacy_policy, R.string.privacy_policy_url + R.string.privacy_policy_title, + R.string.privacy_policy_encouragement, + R.string.read_privacy_policy, + R.string.privacy_policy_url ) ) @@ -86,23 +94,23 @@ fun AboutTab() { Image( imageVector = NewPipeSquircleIcon, contentDescription = stringResource(R.string.app_name), - modifier = Modifier.size(64.dp), + modifier = Modifier.size(64.dp) ) Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.app_name), style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center, + textAlign = TextAlign.Center ) Text( text = BuildConfig.VERSION_NAME, style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + textAlign = TextAlign.Center ) Text( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.app_description), - textAlign = TextAlign.Center, + textAlign = TextAlign.Center ) } @@ -120,7 +128,7 @@ fun AboutTab() { @NonRestartableComposable private fun AboutItem( @PreviewParameter(AboutDataProvider::class) aboutData: AboutData, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { Column(modifier = modifier) { Text( diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/Library.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/Library.kt index 97a2be949..a5277dca0 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/Library.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/Library.kt @@ -39,7 +39,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils fun Library( @PreviewParameter(LibraryProvider::class) library: Library, showLicenseDialog: (licenseFilename: String) -> Unit, - descriptionMaxLines: Int, + descriptionMaxLines: Int ) { val spdxLicense = library.licenses.firstOrNull()?.spdxId?.takeIf { it.isNotBlank() } val licenseAssetPath = spdxLicense?.let { SPDX_ID_TO_ASSET_PATH[it] } @@ -63,14 +63,14 @@ fun Library( ) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = library.name, modifier = Modifier.weight(0.75f), style = MaterialTheme.typography.titleMedium, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) val version = library.artifactVersion if (!version.isNullOrBlank()) { @@ -85,7 +85,7 @@ fun Library( }.padding(start = 8.dp), style = MaterialTheme.typography.labelMedium, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) } } @@ -95,7 +95,7 @@ fun Library( text = author, style = MaterialTheme.typography.bodyMedium, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) } val description = library.description @@ -105,14 +105,14 @@ fun Library( text = description, style = MaterialTheme.typography.bodySmall, maxLines = descriptionMaxLines, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) } if (library.licenses.isNotEmpty()) { FlowRow( modifier = Modifier.padding(top = 6.dp, bottom = 4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { library.licenses.forEach { Badge { diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/LibraryDefinitions.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/LibraryDefinitions.kt index 6ab103c99..9a6bfb7a0 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/LibraryDefinitions.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/LibraryDefinitions.kt @@ -1,4 +1,4 @@ -/** +/* * The library definitions for most libraries are autogenerated by the AboutLibraries plugin. * This file is only for TeamNewPipe-related libraries. */ @@ -22,12 +22,12 @@ val SPDX_ID_TO_ASSET_PATH = mapOf( "GPL-3.0-only" to "gpl_3.html", "GPL-3.0-or-later" to "gpl_3.html", "MIT" to "mit.html", - "MPL-2.0" to "mpl2.html", + "MPL-2.0" to "mpl2.html" ) fun getFirstPartyLibraries( context: Context, - teamNewPipeLibraries: List, + teamNewPipeLibraries: List ): List { val gpl3 = setOf( License( @@ -36,7 +36,7 @@ fun getFirstPartyLibraries( year = null, spdxId = "GPL-3.0-or-later", licenseContent = null, - hash = "GPL-3.0-or-later", + hash = "GPL-3.0-or-later" ) ).toImmutableSet() @@ -58,7 +58,7 @@ fun getFirstPartyLibraries( ).toImmutableList(), organization = null, scm = Scm(null, null, context.getString(R.string.github_url)), - licenses = gpl3, + licenses = gpl3 ), Library( uniqueId = npeId, @@ -74,15 +74,15 @@ fun getFirstPartyLibraries( ).toImmutableList(), organization = null, scm = Scm(null, null, context.getString(R.string.newpipe_extractor_github_url)), - licenses = gpl3, - ), + licenses = gpl3 + ) ) } fun getAdditionalThirdPartyLibraries( context: Context, teamNewPipeLibraries: List, - licenses: ImmutableSet, + licenses: ImmutableSet ): List { val apache2 = licenses.firstOrNull { it.spdxId == "Apache-2.0" } val mit = licenses.firstOrNull { it.spdxId == "MIT" } @@ -103,7 +103,7 @@ fun getAdditionalThirdPartyLibraries( developers = listOf( Developer( name = "Jonas Kalderstam", - organisationUrl = "https://github.com/spacecowboy/NoNonsense-FilePicker", + organisationUrl = "https://github.com/spacecowboy/NoNonsense-FilePicker" ), Developer( name = context.getString(R.string.team_newpipe), @@ -112,7 +112,7 @@ fun getAdditionalThirdPartyLibraries( ).toImmutableList(), organization = null, scm = Scm(null, null, "https://github.com/TeamNewPipe/NoNonsense-FilePicker"), - licenses = listOfNotNull(mpl2).toImmutableSet(), + licenses = listOfNotNull(mpl2).toImmutableSet() ), Library( uniqueId = nanojsonId, @@ -123,16 +123,16 @@ fun getAdditionalThirdPartyLibraries( developers = listOf( Developer( name = "mmastrac", - organisationUrl = "https://github.com/mmastrac/nanojson", + organisationUrl = "https://github.com/mmastrac/nanojson" ), Developer( name = context.getString(R.string.team_newpipe), organisationUrl = context.getString(R.string.website_url) - ), + ) ).toImmutableList(), organization = null, scm = Scm(null, null, "https://github.com/TeamNewPipe/nanojson"), licenses = listOfNotNull(mit, apache2).toImmutableSet() - ), + ) ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseDialog.kt index 24421a93a..bc736e5cd 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseDialog.kt @@ -40,7 +40,7 @@ fun LicenseDialog(licenseHtml: AnnotatedString, onDismissRequest: () -> Unit) { } else { Text( text = licenseHtml, - modifier = Modifier.padding(horizontal = 12.dp), + modifier = Modifier.padding(horizontal = 12.dp) ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTab.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTab.kt index 46e71ba56..39b668cea 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTab.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTab.kt @@ -32,7 +32,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { LazyColumnThemedScrollbar(state = lazyListState) { LazyColumn( - state = lazyListState, + state = lazyListState ) { item { Text( @@ -43,7 +43,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { top = 16.dp, end = 16.dp, bottom = 8.dp - ), + ) ) } item { @@ -54,7 +54,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { start = 16.dp, end = 16.dp, bottom = 8.dp - ), + ) ) } if (state.firstPartyLibraries == null) { @@ -67,7 +67,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { Library( library = library, showLicenseDialog = viewModel::showLicenseDialog, - descriptionMaxLines = Int.MAX_VALUE, + descriptionMaxLines = Int.MAX_VALUE ) } } @@ -82,7 +82,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { top = 16.dp, end = 16.dp, bottom = 8.dp - ), + ) ) } if (state.thirdPartyLibraries == null) { @@ -95,7 +95,7 @@ fun LicenseTab(viewModel: LicenseTabViewModel = viewModel()) { Library( library = library, showLicenseDialog = viewModel::showLicenseDialog, - descriptionMaxLines = 2, + descriptionMaxLines = 2 ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTabViewModel.kt b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTabViewModel.kt index eeb87816c..8b149a6fb 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTabViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseTabViewModel.kt @@ -45,7 +45,7 @@ class LicenseTabViewModel : ViewModel() { _state.update { it.copy( firstPartyLibraries = firstParty, - thirdPartyLibraries = allThirdParty, + thirdPartyLibraries = allThirdParty ) } } @@ -77,6 +77,6 @@ class LicenseTabViewModel : ViewModel() { val firstPartyLibraries: List?, val thirdPartyLibraries: List?, // null if dialog closed, empty if loading, otherwise license HTML content - val licenseDialogHtml: AnnotatedString?, + val licenseDialogHtml: AnnotatedString? ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt index 666d1759d..4cb08a899 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt @@ -29,7 +29,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils fun ErrorPanel( errorInfo: ErrorInfo, modifier: Modifier = Modifier, - onRetry: (() -> Unit)? = null, + onRetry: (() -> Unit)? = null ) { val context = LocalContext.current val isPreview = LocalInspectionMode.current @@ -42,7 +42,7 @@ fun ErrorPanel( Column( verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, + modifier = modifier ) { Text( text = messageText, @@ -97,9 +97,9 @@ private fun ErrorPanelPreview() { throwable = ReCaptchaException("An error", "https://example.com"), userAction = UserAction.REQUESTED_STREAM, request = "Preview request", - openInBrowserUrl = "https://example.com", + openInBrowserUrl = "https://example.com" ), - onRetry = {}, + onRetry = {} ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/ScaffoldWithToolbar.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/ScaffoldWithToolbar.kt index 18139c7a6..4780e78a3 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/ScaffoldWithToolbar.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/ScaffoldWithToolbar.kt @@ -34,7 +34,7 @@ fun ScaffoldWithToolbar( scrolledContainerColor = MaterialTheme.colorScheme.primaryContainer, navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer, titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer, - actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer, + actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer ), navigationIcon = { IconButton(onClick = onBackClick) { diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/Scrollbar.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/Scrollbar.kt index 1e619f32a..d4607fd71 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/Scrollbar.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/Scrollbar.kt @@ -10,7 +10,7 @@ import my.nanihadesuka.compose.ScrollbarSettings @Composable fun defaultThemedScrollbarSettings(): ScrollbarSettings = ScrollbarSettings.Default.copy( thumbUnselectedColor = MaterialTheme.colorScheme.primary, - thumbSelectedColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f), + thumbSelectedColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f) ) @Composable @@ -26,6 +26,6 @@ fun LazyColumnThemedScrollbar( modifier = modifier, settings = settings, indicatorContent = indicatorContent, - content = content, + content = content ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/ServiceColoredButton.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/ServiceColoredButton.kt index 59a37066c..cdec32fc4 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/ServiceColoredButton.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/ServiceColoredButton.kt @@ -21,7 +21,7 @@ import org.schabi.newpipe.ui.theme.SizeTokens.SpacingSmall fun ServiceColoredButton( onClick: () -> Unit, modifier: Modifier = Modifier, - content: @Composable() RowScope.() -> Unit, + content: @Composable RowScope.() -> Unit ) { Button( onClick = onClick, @@ -33,9 +33,9 @@ fun ServiceColoredButton( contentPadding = PaddingValues(horizontal = SpacingMedium, vertical = SpacingSmall), shape = RectangleShape, elevation = ButtonDefaults.buttonElevation( - defaultElevation = 8.dp, + defaultElevation = 8.dp - ), + ) ) { content() } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt index 4562e17af..ba45c503d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt @@ -40,11 +40,20 @@ fun ItemList( val fragmentManager = context.findFragmentActivity().supportFragmentManager if (item is StreamInfoItem) { NavigationHelper.openVideoDetailFragment( - context, fragmentManager, item.serviceId, item.url, item.name, null, false + context, + fragmentManager, + item.serviceId, + item.url, + item.name, + null, + false ) } else if (item is PlaylistInfoItem) { NavigationHelper.openPlaylistFragment( - fragmentManager, item.serviceId, item.url, item.name + fragmentManager, + item.serviceId, + item.url, + item.name ) } } @@ -82,7 +91,12 @@ fun ItemList( if (item is StreamInfoItem) { val isSelected = selectedStream == item StreamListItem( - item, showProgress, isSelected, onClick, onLongClick, onDismissPopup + item, + showProgress, + isSelected, + onClick, + onLongClick, + onDismissPopup ) } else if (item is PlaylistInfoItem) { PlaylistListItem(item, onClick) @@ -109,6 +123,7 @@ private fun determineItemViewMode(): ItemViewMode { ItemViewMode.LIST } } + else -> viewMode } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt index 653886935..c8a9b2d0e 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt @@ -25,7 +25,7 @@ import org.schabi.newpipe.util.NO_SERVICE_ID @Composable fun PlaylistListItem( playlist: PlaylistInfoItem, - onClick: (InfoItem) -> Unit = {}, + onClick: (InfoItem) -> Unit = {} ) { Row( modifier = Modifier diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt index 7619515e7..099a93005 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt @@ -77,7 +77,9 @@ fun StreamMenu( onClick = { onDismissRequest() SparseItemUtil.fetchStreamInfoAndSaveToDatabase( - context, stream.serviceId, stream.url + context, + stream.serviceId, + stream.url ) { info -> // TODO: Use an AlertDialog composable instead. val downloadDialog = DownloadDialog(context, info) @@ -126,7 +128,10 @@ fun StreamMenu( onClick = { onDismissRequest() SparseItemUtil.fetchUploaderUrlIfSparse( - context, stream.serviceId, stream.url, stream.uploaderUrl + context, + stream.serviceId, + stream.url, + stream.uploaderUrl ) { url -> val activity = context.findFragmentActivity() NavigationHelper.openChannelFragment(activity, stream, url) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt index f5515a24a..9ee5e0d3f 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt @@ -23,14 +23,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import coil3.compose.AsyncImage +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.StreamTypeUtil import org.schabi.newpipe.util.image.ImageStrategy import org.schabi.newpipe.viewmodels.StreamViewModel -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds @Composable fun StreamThumbnail( diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt index 628862332..d744b700d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt @@ -4,12 +4,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import java.util.concurrent.TimeUnit import org.schabi.newpipe.extractor.Image import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NO_SERVICE_ID -import java.util.concurrent.TimeUnit @Suppress("ktlint:standard:function-naming") fun StreamInfoItem( @@ -64,6 +64,6 @@ internal class StreamItemPreviewProvider : PreviewParameterProvider Unit) { .animateContentSize() .combinedClickable( onLongClick = copyToClipboardCallback { parsedDescription }, - onClick = { isExpanded = !isExpanded }, + onClick = { isExpanded = !isExpanded } ) .padding(start = 8.dp, top = 10.dp, end = 8.dp, bottom = 4.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -102,7 +102,9 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { Localization.concatenateStrings( Localization.localizeUserName(comment.uploaderName), Localization.relativeTimeOrTextual( - context, comment.uploadDate, comment.textualUploadDate + context, + comment.uploadDate, + comment.textualUploadDate ) ) } @@ -110,7 +112,7 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { text = nameAndDate, style = MaterialTheme.typography.titleSmall, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) } @@ -127,7 +129,7 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -140,7 +142,7 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { contentDescription = stringResource(R.string.detail_likes_img_view_description), modifier = Modifier .padding(end = 4.dp) - .size(20.dp), + .size(20.dp) ) Text( text = Localization.likeCount(context, comment.likeCount), @@ -155,7 +157,7 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { imageVector = Icons.Default.Favorite, contentDescription = stringResource(R.string.detail_heart_img_view_description), tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(20.dp), + modifier = Modifier.size(20.dp) ) } } @@ -169,7 +171,9 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { modifier = Modifier.padding(end = 2.dp) ) { val text = pluralStringResource( - R.plurals.replies, comment.replyCount, comment.replyCount.toString() + R.plurals.replies, + comment.replyCount, + comment.replyCount.toString() ) Text(text = text) } @@ -183,7 +187,7 @@ fun Comment(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Unit) { CommentRepliesDialog( parentComment = comment, onDismissRequest = { showReplies = false }, - onCommentAuthorOpened = onCommentAuthorOpened, + onCommentAuthorOpened = onCommentAuthorOpened ) } } @@ -200,7 +204,7 @@ fun CommentsInfoItem( isHeartedByUploader: Boolean = false, isPinned: Boolean = false, replies: Page? = null, - replyCount: Int = 0, + replyCount: Int = 0 ) = CommentsInfoItem(serviceId, url, name).apply { this.commentText = commentText this.uploaderName = uploaderName @@ -249,7 +253,7 @@ private class CommentPreviewProvider : CollectionPreviewParameterProvider Unit, - onCommentAuthorOpened: () -> Unit, + onCommentAuthorOpened: () -> Unit ) { val coroutineScope = rememberCoroutineScope() val commentsFlow = remember { @@ -65,7 +65,7 @@ private fun CommentRepliesDialog( parentComment: CommentsInfoItem, commentsFlow: Flow>, onDismissRequest: () -> Unit, - onCommentAuthorOpened: () -> Unit, + onCommentAuthorOpened: () -> Unit ) { val comments = commentsFlow.collectAsLazyPagingItems() val nestedScrollInterop = rememberNestedScrollInteropConnection() @@ -83,7 +83,7 @@ private fun CommentRepliesDialog( ModalBottomSheet( sheetState = sheetState, - onDismissRequest = onDismissRequest, + onDismissRequest = onDismissRequest ) { LazyColumnThemedScrollbar(state = listState) { LazyColumn( @@ -93,7 +93,7 @@ private fun CommentRepliesDialog( item { CommentRepliesHeader( comment = parentComment, - onCommentAuthorOpened = nestedOnCommentAuthorOpened, + onCommentAuthorOpened = nestedOnCommentAuthorOpened ) HorizontalDivider( thickness = 1.dp, @@ -111,7 +111,7 @@ private fun CommentRepliesDialog( text = pluralStringResource( R.plurals.replies, parentComment.replyCount, - parentComment.replyCount, + parentComment.replyCount ), maxLines = 1, style = MaterialTheme.typography.titleMedium @@ -125,6 +125,7 @@ private fun CommentRepliesDialog( is LoadState.Loading -> { LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) } + else -> { // TODO use error panel instead EmptyStateComposable( @@ -144,7 +145,7 @@ private fun CommentRepliesDialog( items(comments.itemCount) { Comment( comment = comments[it]!!, - onCommentAuthorOpened = nestedOnCommentAuthorOpened, + onCommentAuthorOpened = nestedOnCommentAuthorOpened ) } } @@ -168,7 +169,7 @@ private fun CommentRepliesDialogPreview() { CommentsInfoItem( commentText = Description( "Reply $i: ${LoremIpsum(i * i).values.first()}", - Description.PLAIN_TEXT, + Description.PLAIN_TEXT ), uploaderName = LoremIpsum(11 - i).values.first() ) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesHeader.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesHeader.kt index f9c44b80c..4530ebca6 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesHeader.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesHeader.kt @@ -44,7 +44,7 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Column( modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), @@ -61,7 +61,7 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> } .weight(1.0f, true), horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, + verticalAlignment = Alignment.CenterVertically ) { AsyncImage( model = ImageStrategy.choosePreferredImage(comment.uploaderAvatars), @@ -78,11 +78,13 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> text = comment.uploaderName, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleSmall, + style = MaterialTheme.typography.titleSmall ) Localization.relativeTimeOrTextual( - context, comment.uploadDate, comment.textualUploadDate + context, + comment.uploadDate, + comment.textualUploadDate )?.let { Text(text = it, style = MaterialTheme.typography.bodySmall) } @@ -97,11 +99,11 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> if (comment.likeCount >= 0) { Icon( imageVector = Icons.Default.ThumbUp, - contentDescription = stringResource(R.string.detail_likes_img_view_description), + contentDescription = stringResource(R.string.detail_likes_img_view_description) ) Text( text = Localization.likeCount(context, comment.likeCount), - maxLines = 1, + maxLines = 1 ) } @@ -109,14 +111,14 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> Icon( imageVector = Icons.Default.Favorite, contentDescription = stringResource(R.string.detail_heart_img_view_description), - tint = MaterialTheme.colorScheme.primary, + tint = MaterialTheme.colorScheme.primary ) } if (comment.isPinned) { Icon( imageVector = Icons.Default.PushPin, - contentDescription = stringResource(R.string.detail_pinned_comment_view_description), + contentDescription = stringResource(R.string.detail_pinned_comment_view_description) ) } } @@ -124,7 +126,7 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, onCommentAuthorOpened: () -> DescriptionText( description = comment.commentText, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt index f0a293784..b2e7b5813 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt @@ -111,6 +111,7 @@ private fun CommentSection( LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) } } + is LoadState.Error -> { val errorInfo = ErrorInfo( throwable = refresh.error, @@ -131,6 +132,7 @@ private fun CommentSection( } } } + else -> { items(comments.itemCount) { Comment(comment = comments[it]!!) {} @@ -139,6 +141,7 @@ private fun CommentSection( } } } + is Resource.Error -> { val errorInfo = ErrorInfo( throwable = uiState.throwable, @@ -201,8 +204,12 @@ private fun CommentSectionSuccessPreview() { CommentSection( uiState = Resource.Success( CommentInfo( - serviceId = 1, url = "", comments = comments, nextPage = null, - commentCount = 10, isCommentsDisabled = false + serviceId = 1, + url = "", + comments = comments, + nextPage = null, + commentCount = 10, + isCommentsDisabled = false ) ), commentsFlow = flowOf(PagingData.from(comments)) diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index 77fa02082..0b6c09be2 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -22,17 +22,17 @@ import org.schabi.newpipe.ui.theme.AppTheme @Composable fun EmptyStateComposable( spec: EmptyStateSpec, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { Column( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.Center ) { Text( text = spec.emojiText, style = MaterialTheme.typography.titleLarge, - textAlign = TextAlign.Center, + textAlign = TextAlign.Center ) Text( @@ -41,7 +41,7 @@ fun EmptyStateComposable( .padding(horizontal = 16.dp), text = stringResource(spec.descriptionText), style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, + textAlign = TextAlign.Center ) } } @@ -74,46 +74,46 @@ fun EmptyStateComposableNoCommentPreview() { enum class EmptyStateSpec( val emojiText: String, - @field:StringRes val descriptionText: Int, + @field:StringRes val descriptionText: Int ) { GenericError( emojiText = "¯\\_(ツ)_/¯", - descriptionText = R.string.empty_list_subtitle, + descriptionText = R.string.empty_list_subtitle ), NoVideos( emojiText = "(╯°-°)╯", - descriptionText = R.string.no_videos, + descriptionText = R.string.no_videos ), NoComments( emojiText = "¯\\_(╹x╹)_/¯", - descriptionText = R.string.no_comments, + descriptionText = R.string.no_comments ), DisabledComments( emojiText = "¯\\_(╹x╹)_/¯", - descriptionText = R.string.comments_are_disabled, + descriptionText = R.string.comments_are_disabled ), ErrorLoadingComments( emojiText = "¯\\_(╹x╹)_/¯", - descriptionText = R.string.error_unable_to_load_comments, + descriptionText = R.string.error_unable_to_load_comments ), NoSearchResult( emojiText = "╰(°●°╰)", - descriptionText = R.string.search_no_results, + descriptionText = R.string.search_no_results ), ContentNotSupported( emojiText = "(︶︹︺)", - descriptionText = R.string.content_not_supported, + descriptionText = R.string.content_not_supported ), NoBookmarkedPlaylist( emojiText = "(╥﹏╥)", - descriptionText = R.string.no_playlist_bookmarked_yet, + descriptionText = R.string.no_playlist_bookmarked_yet ), NoSubscriptionsHint( emojiText = "(꩜ᯅ꩜)", - descriptionText = R.string.import_subscriptions_hint, + descriptionText = R.string.import_subscriptions_hint ), NoSubscriptions( emojiText = "(꩜ᯅ꩜)", - descriptionText = R.string.no_channel_subscribed_yet, - ), + descriptionText = R.string.no_channel_subscribed_yet + ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index 3e030407c..75e013ad1 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -17,7 +17,7 @@ import org.schabi.newpipe.ui.theme.AppTheme @JvmOverloads fun ComposeView.setEmptyStateComposable( spec: EmptyStateSpec = EmptyStateSpec.GenericError, - strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, + strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) = apply { setViewCompositionStrategy(strategy) setContent { diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt index d436b35a2..208dbc895 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/Theme.kt @@ -44,7 +44,7 @@ private val lightScheme = lightColorScheme( surfaceContainerLow = surfaceContainerLowLight, surfaceContainer = surfaceContainerLight, surfaceContainerHigh = surfaceContainerHighLight, - surfaceContainerHighest = surfaceContainerHighestLight, + surfaceContainerHighest = surfaceContainerHighestLight ) private val darkScheme = darkColorScheme( @@ -82,7 +82,7 @@ private val darkScheme = darkColorScheme( surfaceContainerLow = surfaceContainerLowDark, surfaceContainer = surfaceContainerDark, surfaceContainerHigh = surfaceContainerHighDark, - surfaceContainerHighest = surfaceContainerHighestDark, + surfaceContainerHighest = surfaceContainerHighestDark ) private val blackScheme = darkScheme.copy(surface = Color.Black) diff --git a/app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt b/app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt index b3bd077f8..a191f2f01 100644 --- a/app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt +++ b/app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.sync.withLock * */ class GuardedByMutex( private var data: T, - private val lock: Mutex = Mutex(locked = false), + private val lock: Mutex = Mutex(locked = false) ) { /** Lock the mutex and access the data, blocking the current thread. @@ -18,20 +18,18 @@ class GuardedByMutex( * */ fun runWithLockSync( action: MutexData.() -> Y - ) = - runBlocking { - lock.withLock { - MutexData(data, { d -> data = d }).action() - } + ) = runBlocking { + lock.withLock { + MutexData(data, { d -> data = d }).action() } + } /** Lock the mutex and access the data, suspending the coroutine. * @param action to run with locked mutex * */ - suspend fun runWithLock(action: MutexData.() -> Y) = - lock.withLock { - MutexData(data, { d -> data = d }).action() - } + suspend fun runWithLock(action: MutexData.() -> Y) = lock.withLock { + MutexData(data, { d -> data = d }).action() + } } /** The data inside a [GuardedByMutex], which can be accessed via [lockData]. diff --git a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt index 5b393658c..bd1c57f98 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt @@ -17,11 +17,11 @@ import coil3.size.Size import coil3.target.Target import coil3.toBitmap import coil3.transform.Transformation +import kotlin.math.min import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Image import org.schabi.newpipe.ktx.scale -import kotlin.math.min object CoilHelper { private val TAG = CoilHelper::class.java.simpleName @@ -30,37 +30,36 @@ object CoilHelper { fun loadBitmapBlocking( context: Context, url: String?, - @DrawableRes placeholderResId: Int = 0, - ): Bitmap? = - context.imageLoader - .executeBlocking(getImageRequest(context, url, placeholderResId).build()) - .image - ?.toBitmap() + @DrawableRes placeholderResId: Int = 0 + ): Bitmap? = context.imageLoader + .executeBlocking(getImageRequest(context, url, placeholderResId).build()) + .image + ?.toBitmap() fun loadAvatar( target: ImageView, - images: List, + images: List ) { loadImageDefault(target, images, R.drawable.placeholder_person) } fun loadAvatar( target: ImageView, - url: String?, + url: String? ) { loadImageDefault(target, url, R.drawable.placeholder_person) } fun loadThumbnail( target: ImageView, - images: List, + images: List ) { loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video) } fun loadThumbnail( target: ImageView, - url: String?, + url: String? ) { loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video) } @@ -68,7 +67,7 @@ object CoilHelper { fun loadScaledDownThumbnail( context: Context, images: List, - target: Target, + target: Target ): Disposable { val url = ImageStrategy.choosePreferredImage(images) val request = @@ -80,7 +79,7 @@ object CoilHelper { override suspend fun transform( input: Bitmap, - size: Size, + size: Size ): Bitmap { if (MainActivity.DEBUG) { Log.d(TAG, "Thumbnail - transform() called") @@ -89,7 +88,7 @@ object CoilHelper { val notificationThumbnailWidth = min( context.resources.getDimension(R.dimen.player_notification_thumbnail_width), - input.width.toFloat(), + input.width.toFloat() ).toInt() var newHeight = input.height / (input.width / notificationThumbnailWidth) @@ -104,7 +103,7 @@ object CoilHelper { result } } - }, + } ).build() return context.imageLoader.enqueue(request) @@ -112,7 +111,7 @@ object CoilHelper { fun loadDetailsThumbnail( target: ImageView, - images: List, + images: List ) { val url = ImageStrategy.choosePreferredImage(images) loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false) @@ -120,21 +119,21 @@ object CoilHelper { fun loadBanner( target: ImageView, - images: List, + images: List ) { loadImageDefault(target, images, R.drawable.placeholder_channel_banner) } fun loadPlaylistThumbnail( target: ImageView, - images: List, + images: List ) { loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist) } fun loadPlaylistThumbnail( target: ImageView, - url: String?, + url: String? ) { loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist) } @@ -142,7 +141,7 @@ object CoilHelper { private fun loadImageDefault( target: ImageView, images: List, - @DrawableRes placeholderResId: Int, + @DrawableRes placeholderResId: Int ) { loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId) } @@ -151,7 +150,7 @@ object CoilHelper { target: ImageView, url: String?, @DrawableRes placeholderResId: Int, - showPlaceholder: Boolean = true, + showPlaceholder: Boolean = true ) { val request = getImageRequest(target.context, url, placeholderResId, showPlaceholder) @@ -164,7 +163,7 @@ object CoilHelper { context: Context, url: String?, @DrawableRes placeholderResId: Int, - showPlaceholderWhileLoading: Boolean = true, + showPlaceholderWhileLoading: Boolean = true ): ImageRequest.Builder { // if the URL was chosen with `choosePreferredImage` it will be null, but check again // `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case diff --git a/app/src/main/java/org/schabi/newpipe/util/image/NewPipeSquircleIcon.kt b/app/src/main/java/org/schabi/newpipe/util/image/NewPipeSquircleIcon.kt index a4ff131a1..18c1a9f47 100644 --- a/app/src/main/java/org/schabi/newpipe/util/image/NewPipeSquircleIcon.kt +++ b/app/src/main/java/org/schabi/newpipe/util/image/NewPipeSquircleIcon.kt @@ -23,11 +23,11 @@ val NewPipeSquircleIcon: ImageVector defaultWidth = 100.0.dp, defaultHeight = 100.0.dp, viewportWidth = 100.0f, - viewportHeight = 100.0f, + viewportHeight = 100.0f ).apply { // M0 50 C0 15 15 0 50 0 s50 15 50 50 -15 50 -50 50 S0 85 0 50 path( - fill = SolidColor(Color(0xFFCD201F)), + fill = SolidColor(Color(0xFFCD201F)) ) { // M 0 50 moveTo(x = 0.0f, y = 50.0f) @@ -38,33 +38,33 @@ val NewPipeSquircleIcon: ImageVector x2 = 15.0f, y2 = 0.0f, x3 = 50.0f, - y3 = 0.0f, + y3 = 0.0f ) // s 50 15 50 50 reflectiveCurveToRelative( dx1 = 50.0f, dy1 = 15.0f, dx2 = 50.0f, - dy2 = 50.0f, + dy2 = 50.0f ) // s -15 50 -50 50 reflectiveCurveToRelative( dx1 = -15.0f, dy1 = 50.0f, dx2 = -50.0f, - dy2 = 50.0f, + dy2 = 50.0f ) // S 0 85 0 50 reflectiveCurveTo( x1 = 0.0f, y1 = 85.0f, x2 = 0.0f, - y2 = 50.0f, + y2 = 50.0f ) } // M31.7 19.2 v61.7 l9.7 -5.73 V36 l23.8 14 -17.6 10.35 V71.5 L84 50 path( - fill = SolidColor(Color(0xFFFFFFFF)), + fill = SolidColor(Color(0xFFFFFFFF)) ) { // M 31.7 19.2 moveTo(x = 31.7f, y = 19.2f) @@ -91,7 +91,7 @@ val NewPipeSquircleIcon: ImageVector private fun IconPreview() { Image( imageVector = NewPipeSquircleIcon, - contentDescription = null, + contentDescription = null ) } 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 2d8a29acb..7a90a4721 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,11 @@ package org.schabi.newpipe.settings import android.content.SharedPreferences +import java.io.File +import java.io.IOException +import kotlin.io.path.createTempFile +import kotlin.io.path.exists +import kotlin.io.path.fileSize import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -8,11 +13,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 kotlin.io.path.createTempFile -import kotlin.io.path.exists -import kotlin.io.path.fileSize class ImportAllCombinationsTest { @@ -23,7 +23,7 @@ class ImportAllCombinationsTest { private enum class Ser(val id: String) { YES("ser"), VULNERABLE("vulnser"), - NO("noser"); + NO("noser") } private data class FailData( @@ -31,7 +31,7 @@ class ImportAllCombinationsTest { val containsSer: Ser, val containsJson: Boolean, val filename: String, - val throwable: Throwable, + val throwable: Throwable ) private fun testZipCombination( @@ -39,7 +39,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()) @@ -95,6 +95,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) { @@ -104,6 +105,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) { @@ -154,15 +156,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 ) ) } @@ -175,7 +180,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 a1afcedc1..482b38237 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt +++ b/app/src/test/java/org/schabi/newpipe/settings/ImportExportManagerTest.kt @@ -4,8 +4,15 @@ import android.content.SharedPreferences import com.grack.nanojson.JsonParser import java.io.File import java.io.ObjectInputStream -import java.nio.file.Files +import java.nio.file.Paths import java.util.zip.ZipFile +import kotlin.io.path.createTempDirectory +import kotlin.io.path.createTempFile +import kotlin.io.path.deleteIfExists +import kotlin.io.path.div +import kotlin.io.path.exists +import kotlin.io.path.fileSize +import kotlin.io.path.inputStream import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertThrows @@ -27,20 +34,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 -<<<<<<< HEAD -import java.io.File -import java.io.ObjectInputStream -import java.nio.file.Paths -import java.util.zip.ZipFile -import kotlin.io.path.createTempDirectory -import kotlin.io.path.createTempFile -import kotlin.io.path.deleteIfExists -import kotlin.io.path.div -import kotlin.io.path.exists -import kotlin.io.path.fileSize -import kotlin.io.path.inputStream -======= ->>>>>>> dev @RunWith(MockitoJUnitRunner::class) class ImportExportManagerTest {