Fix issues reported by detekt

This commit is contained in:
Maxr1998 authored and Niels van Velzen committed 2022-04-06 14:35:07 +02:00
1 parent 6c5a90218d
commit 2a08c82bcc
16 files changed
+214 -176

No files matched your search

@@ -15,15 +15,15 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import org.jellyfin.mobile.player.cast.Chromecast
import org.jellyfin.mobile.player.cast.IChromecast
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.player.ui.PlayerFragment
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.PermissionRequestHelper
import org.jellyfin.mobile.utils.SmartOrientationListener
import org.jellyfin.mobile.utils.isWebViewSupported
import org.jellyfin.mobile.utils.extensions.replaceFragment
import org.jellyfin.mobile.utils.isWebViewSupported
import org.jellyfin.mobile.webapp.RemotePlayerService
import org.jellyfin.mobile.webapp.WebViewFragment
import org.koin.android.ext.android.inject
import org.koin.androidx.fragment.android.setupKoinFragmentFactory
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -87,9 +87,11 @@ class MainActivity : AppCompatActivity() {
is ServerState.Available -> {
val currentFragment = findFragmentById(R.id.fragment_container)
if (currentFragment !is WebViewFragment || currentFragment.server != state.server) {
replaceFragment<WebViewFragment>(Bundle().apply {
putParcelable(Constants.FRAGMENT_WEB_VIEW_EXTRA_SERVER, state.server)
})
replaceFragment<WebViewFragment>(
Bundle().apply {
putParcelable(Constants.FRAGMENT_WEB_VIEW_EXTRA_SERVER, state.server)
}
)
}
}
}
@@ -16,18 +16,18 @@ import com.google.android.exoplayer2.util.Util
import kotlinx.coroutines.channels.Channel
import okhttp3.OkHttpClient
import org.jellyfin.mobile.MainViewModel
import org.jellyfin.mobile.player.deviceprofile.DeviceProfileBuilder
import org.jellyfin.mobile.bridge.ExternalPlayer
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.player.audio.car.LibraryBrowser
import org.jellyfin.mobile.player.deviceprofile.DeviceProfileBuilder
import org.jellyfin.mobile.player.interaction.PlayerEvent
import org.jellyfin.mobile.player.ui.PlayerFragment
import org.jellyfin.mobile.player.source.MediaSourceResolver
import org.jellyfin.mobile.player.ui.PlayerFragment
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.PermissionRequestHelper
import org.jellyfin.mobile.utils.isLowRamDevice
import org.jellyfin.mobile.webapp.RemoteVolumeProvider
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.webapp.WebappFunctionChannel
import org.koin.android.ext.koin.androidApplication
import org.koin.androidx.fragment.dsl.fragment
@@ -11,7 +11,6 @@ import android.webkit.JavascriptInterface
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.jellyfin.mobile.R
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.settings.SettingsFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.EXTRA_ALBUM
@@ -28,11 +27,12 @@ import org.jellyfin.mobile.utils.Constants.EXTRA_TITLE
import org.jellyfin.mobile.utils.extensions.addFragment
import org.jellyfin.mobile.utils.extensions.disableFullscreen
import org.jellyfin.mobile.utils.extensions.enableFullscreen
import org.jellyfin.mobile.utils.requestDownload
import org.jellyfin.mobile.utils.extensions.requireMainActivity
import org.jellyfin.mobile.utils.requestDownload
import org.jellyfin.mobile.utils.runOnUiThread
import org.jellyfin.mobile.webapp.RemotePlayerService
import org.jellyfin.mobile.webapp.RemoteVolumeProvider
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.webapp.WebappFunctionChannel
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.util.AuthorizationHeaderBuilder
@@ -191,10 +191,14 @@ class NativeInterface(private val fragment: WebViewFragment) : KoinComponent {
@JavascriptInterface
fun execCast(action: String, args: String) {
fragment.requireMainActivity().chromecast.execute(action, JSONArray(args), object : JavascriptCallback() {
override fun callback(keep: Boolean, err: String?, result: String?) {
webappFunctionChannel.call("""window.NativeShell.castCallback("$action", $keep, $err, $result);""")
}
})
fragment.requireMainActivity().chromecast.execute(
action,
JSONArray(args),
object : JavascriptCallback() {
override fun callback(keep: Boolean, err: String?, result: String?) {
webappFunctionChannel.call("""window.NativeShell.castCallback("$action", $keep, $err, $result);""")
}
},
)
}
}
@@ -29,25 +29,25 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jellyfin.mobile.BuildConfig
import org.jellyfin.mobile.app.PLAYER_EVENT_CHANNEL
import org.jellyfin.mobile.player.ui.DisplayPreferences
import org.jellyfin.mobile.player.interaction.PlayerEvent
import org.jellyfin.mobile.player.interaction.PlayerLifecycleObserver
import org.jellyfin.mobile.player.interaction.PlayerMediaSessionCallback
import org.jellyfin.mobile.player.interaction.PlayerNotificationHelper
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.queue.QueueManager
import org.jellyfin.mobile.player.source.JellyfinMediaSource
import org.jellyfin.mobile.player.ui.DisplayPreferences
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.SUPPORTED_VIDEO_PLAYER_PLAYBACK_ACTIONS
import org.jellyfin.mobile.utils.applyDefaultAudioAttributes
import org.jellyfin.mobile.utils.applyDefaultLocalAudioAttributes
import org.jellyfin.mobile.utils.extensions.scaleInRange
import org.jellyfin.mobile.utils.extensions.width
import org.jellyfin.mobile.utils.getVolumeLevelPercent
import org.jellyfin.mobile.utils.getVolumeRange
import org.jellyfin.mobile.utils.logTracks
import org.jellyfin.mobile.utils.extensions.scaleInRange
import org.jellyfin.mobile.utils.seekToOffset
import org.jellyfin.mobile.utils.setPlaybackState
import org.jellyfin.mobile.utils.toMediaMetadata
import org.jellyfin.mobile.utils.extensions.width
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi
@@ -36,13 +36,13 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.jellyfin.mobile.R
import org.jellyfin.mobile.player.cast.CastPlayerProvider
import org.jellyfin.mobile.player.cast.ICastPlayerProvider
import org.jellyfin.mobile.app.ApiClientController
import org.jellyfin.mobile.player.audio.car.LibraryBrowser
import org.jellyfin.mobile.player.audio.car.LibraryPage
import org.jellyfin.mobile.utils.extensions.mediaUri
import org.jellyfin.mobile.player.cast.CastPlayerProvider
import org.jellyfin.mobile.player.cast.ICastPlayerProvider
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.extensions.mediaUri
import org.jellyfin.mobile.utils.toast
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
@@ -142,7 +142,6 @@ class MediaService : MediaBrowserServiceCompat() {
newPlayer = if (castPlayerProvider.isCastSessionAvailable) castPlayerProvider.get()!! else exoPlayer
)
notificationManager.showNotificationForPlayer(currentPlayer)
}
override fun onDestroy() {
@@ -262,10 +261,11 @@ class MediaService : MediaBrowserServiceCompat() {
}
private fun setPlaybackError() {
mediaSession.setPlaybackState(PlaybackStateCompat.Builder().apply {
setState(PlaybackStateCompat.STATE_ERROR, 0, 1f)
setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED, getString(R.string.media_service_item_not_found))
}.build())
val errorState = PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_ERROR, 0, 1f)
.setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED, getString(R.string.media_service_item_not_found))
.build()
mediaSession.setPlaybackState(errorState)
}
@Suppress("unused")
@@ -46,7 +46,7 @@ import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
import java.util.*
import java.util.UUID
@Suppress("TooManyFunctions")
class LibraryBrowser(
@@ -259,10 +259,12 @@ class LibraryBrowser(
setTitle(context.getString(item.second))
if (item.first == LibraryPage.ALBUMS) {
setExtras(Bundle().apply {
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
})
setExtras(
Bundle().apply {
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
}
)
}
}.build()
MediaBrowserCompat.MediaItem(description, FLAG_BROWSABLE)
@@ -168,7 +168,6 @@ object CodecHelpers {
else -> null
}
private fun getAVCLevel(level: Int): String? = when (level) {
CodecProfileLevel.AVCLevel1 -> "1"
CodecProfileLevel.AVCLevel11 -> "11"
@@ -20,10 +20,10 @@ import com.google.android.exoplayer2.Player
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.BuildConfig
import org.jellyfin.mobile.MainActivity
import org.jellyfin.mobile.R
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.player.PlayerViewModel
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.VIDEO_PLAYER_NOTIFICATION_ID
@@ -114,15 +114,18 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom
}
if (receiverRegistered.compareAndSet(false, true)) {
context.registerReceiver(notificationActionReceiver, IntentFilter().apply {
addAction(Constants.ACTION_PLAY)
addAction(Constants.ACTION_PAUSE)
addAction(Constants.ACTION_REWIND)
addAction(Constants.ACTION_FAST_FORWARD)
addAction(Constants.ACTION_PREVIOUS)
addAction(Constants.ACTION_NEXT)
addAction(Constants.ACTION_STOP)
})
context.registerReceiver(
notificationActionReceiver,
IntentFilter().apply {
addAction(Constants.ACTION_PLAY)
addAction(Constants.ACTION_PAUSE)
addAction(Constants.ACTION_REWIND)
addAction(Constants.ACTION_FAST_FORWARD)
addAction(Constants.ACTION_PREVIOUS)
addAction(Constants.ACTION_NEXT)
addAction(Constants.ACTION_STOP)
},
)
}
}
@@ -28,22 +28,22 @@ import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.PlayerView
import kotlinx.coroutines.launch
import org.jellyfin.mobile.R
import org.jellyfin.mobile.utils.extensions.aspectRational
import org.jellyfin.mobile.utils.extensions.isLandscape
import org.jellyfin.mobile.player.interaction.PlayOptions
import org.jellyfin.mobile.databinding.ExoPlayerControlViewBinding
import org.jellyfin.mobile.databinding.FragmentPlayerBinding
import org.jellyfin.mobile.player.PlayerException
import org.jellyfin.mobile.player.PlayerViewModel
import org.jellyfin.mobile.player.interaction.PlayOptions
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.DEFAULT_CONTROLS_TIMEOUT_MS
import org.jellyfin.mobile.utils.Constants.PIP_MAX_RATIONAL
import org.jellyfin.mobile.utils.Constants.PIP_MIN_RATIONAL
import org.jellyfin.mobile.utils.SmartOrientationListener
import org.jellyfin.mobile.utils.brightness
import org.jellyfin.mobile.utils.extensions.aspectRational
import org.jellyfin.mobile.utils.extensions.disableFullscreen
import org.jellyfin.mobile.utils.extensions.enableFullscreen
import org.jellyfin.mobile.utils.extensions.isFullscreen
import org.jellyfin.mobile.utils.extensions.isLandscape
import org.jellyfin.mobile.utils.toast
import org.jellyfin.sdk.model.api.MediaStream
@@ -73,136 +73,150 @@ class PlayerGestureHelper(
/**
* Handles taps when controls are locked
*/
private val unlockDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
playerLockScreenHelper.peekUnlockButton()
return true
}
})
private val unlockDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
playerLockScreenHelper.peekUnlockButton()
return true
}
},
)
/**
* Handles double tap to seek and brightness/volume gestures
*/
private val gestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewWidth = playerView.measuredWidth
val viewHeight = playerView.measuredHeight
val viewCenterX = viewWidth / 2
val viewCenterY = viewHeight / 2
val isFastForward = e.x.toInt() > viewCenterX
private val gestureDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewWidth = playerView.measuredWidth
val viewHeight = playerView.measuredHeight
val viewCenterX = viewWidth / 2
val viewCenterY = viewHeight / 2
val isFastForward = e.x.toInt() > viewCenterX
// Show ripple effect
playerView.foreground?.apply {
val left = if (isFastForward) viewCenterX else 0
val right = if (isFastForward) viewWidth else viewCenterX
setBounds(left, viewCenterY - viewCenterX / 2, right, viewCenterY + viewCenterX / 2)
setHotspot(e.x, e.y)
state = intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed)
playerView.postDelayed(Constants.DOUBLE_TAP_RIPPLE_DURATION_MS) {
state = IntArray(0)
}
}
// Fast-forward/rewind
with(fragment) { if (isFastForward) onFastForward() else onRewind() }
// Cancel previous runnable to not hide controller while seeking
playerView.removeCallbacks(hidePlayerViewControllerAction)
// Ensure controller gets hidden after seeking
playerView.postDelayed(hidePlayerViewControllerAction, Constants.DEFAULT_CONTROLS_TIMEOUT_MS.toLong())
return true
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
playerView.apply {
if (!isControllerVisible) showController() else hideController()
}
return true
}
override fun onScroll(firstEvent: MotionEvent, currentEvent: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if (!appPreferences.exoPlayerAllowSwipeGestures)
return false
// Check whether swipe was started in excluded region
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
return false
// Check whether swipe was oriented vertically
if (abs(distanceY / distanceX) < 2)
return false
val viewCenterX = playerView.measuredWidth / 2
// Distance to swipe to go from min to max
val distanceFull = playerView.measuredHeight * Constants.FULL_SWIPE_RANGE_SCREEN_RATIO
val ratioChange = distanceY / distanceFull
if (firstEvent.x.toInt() > viewCenterX) {
// Swiping on the right, change volume
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (swipeGestureValueTracker == -1f) swipeGestureValueTracker = currentVolume.toFloat()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val change = ratioChange * maxVolume
swipeGestureValueTracker += change
val toSet = swipeGestureValueTracker.toInt().coerceIn(0, maxVolume)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, toSet, 0)
gestureIndicatorOverlayImage.setImageResource(org.jellyfin.mobile.R.drawable.ic_volume_white_24dp)
gestureIndicatorOverlayProgress.max = maxVolume
gestureIndicatorOverlayProgress.progress = toSet
} else {
// Swiping on the left, change brightness
val window = fragment.requireActivity().window
val brightnessRange = BRIGHTNESS_OVERRIDE_OFF..BRIGHTNESS_OVERRIDE_FULL
// Initialize on first swipe
if (swipeGestureValueTracker == -1f) {
val brightness = window.brightness
swipeGestureValueTracker = when (brightness) {
in brightnessRange -> brightness
else -> Settings.System.getFloat(fragment.requireActivity().contentResolver, Settings.System.SCREEN_BRIGHTNESS) / Constants.SCREEN_BRIGHTNESS_MAX
// Show ripple effect
playerView.foreground?.apply {
val left = if (isFastForward) viewCenterX else 0
val right = if (isFastForward) viewWidth else viewCenterX
setBounds(left, viewCenterY - viewCenterX / 2, right, viewCenterY + viewCenterX / 2)
setHotspot(e.x, e.y)
state = intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed)
playerView.postDelayed(Constants.DOUBLE_TAP_RIPPLE_DURATION_MS) {
state = IntArray(0)
}
}
swipeGestureValueTracker = (swipeGestureValueTracker + ratioChange).coerceIn(brightnessRange)
window.brightness = swipeGestureValueTracker
if (appPreferences.exoPlayerRememberBrightness) {
appPreferences.exoPlayerBrightness = swipeGestureValueTracker
}
// Fast-forward/rewind
with(fragment) { if (isFastForward) onFastForward() else onRewind() }
gestureIndicatorOverlayImage.setImageResource(org.jellyfin.mobile.R.drawable.ic_brightness_white_24dp)
gestureIndicatorOverlayProgress.max = Constants.PERCENT_MAX
gestureIndicatorOverlayProgress.progress = (swipeGestureValueTracker * Constants.PERCENT_MAX).toInt()
// Cancel previous runnable to not hide controller while seeking
playerView.removeCallbacks(hidePlayerViewControllerAction)
// Ensure controller gets hidden after seeking
playerView.postDelayed(hidePlayerViewControllerAction, Constants.DEFAULT_CONTROLS_TIMEOUT_MS.toLong())
return true
}
gestureIndicatorOverlayLayout.isVisible = true
return true
}
})
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
playerView.apply {
if (!isControllerVisible) showController() else hideController()
}
return true
}
override fun onScroll(
firstEvent: MotionEvent,
currentEvent: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
if (!appPreferences.exoPlayerAllowSwipeGestures)
return false
// Check whether swipe was started in excluded region
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
return false
// Check whether swipe was oriented vertically
if (abs(distanceY / distanceX) < 2)
return false
val viewCenterX = playerView.measuredWidth / 2
// Distance to swipe to go from min to max
val distanceFull = playerView.measuredHeight * Constants.FULL_SWIPE_RANGE_SCREEN_RATIO
val ratioChange = distanceY / distanceFull
if (firstEvent.x.toInt() > viewCenterX) {
// Swiping on the right, change volume
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (swipeGestureValueTracker == -1f) swipeGestureValueTracker = currentVolume.toFloat()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val change = ratioChange * maxVolume
swipeGestureValueTracker += change
val toSet = swipeGestureValueTracker.toInt().coerceIn(0, maxVolume)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, toSet, 0)
gestureIndicatorOverlayImage.setImageResource(org.jellyfin.mobile.R.drawable.ic_volume_white_24dp)
gestureIndicatorOverlayProgress.max = maxVolume
gestureIndicatorOverlayProgress.progress = toSet
} else {
// Swiping on the left, change brightness
val window = fragment.requireActivity().window
val brightnessRange = BRIGHTNESS_OVERRIDE_OFF..BRIGHTNESS_OVERRIDE_FULL
// Initialize on first swipe
if (swipeGestureValueTracker == -1f) {
val brightness = window.brightness
swipeGestureValueTracker = when (brightness) {
in brightnessRange -> brightness
else -> Settings.System.getFloat(fragment.requireActivity().contentResolver, Settings.System.SCREEN_BRIGHTNESS) / Constants.SCREEN_BRIGHTNESS_MAX
}
}
swipeGestureValueTracker = (swipeGestureValueTracker + ratioChange).coerceIn(brightnessRange)
window.brightness = swipeGestureValueTracker
if (appPreferences.exoPlayerRememberBrightness) {
appPreferences.exoPlayerBrightness = swipeGestureValueTracker
}
gestureIndicatorOverlayImage.setImageResource(org.jellyfin.mobile.R.drawable.ic_brightness_white_24dp)
gestureIndicatorOverlayProgress.max = Constants.PERCENT_MAX
gestureIndicatorOverlayProgress.progress = (swipeGestureValueTracker * Constants.PERCENT_MAX).toInt()
}
gestureIndicatorOverlayLayout.isVisible = true
return true
}
},
)
/**
* Handles scale/zoom gesture
*/
private val zoomGestureDetector = ScaleGestureDetector(playerView.context, object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = fragment.isLandscape()
private val zoomGestureDetector = ScaleGestureDetector(
playerView.context,
object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = fragment.isLandscape()
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (abs(scaleFactor - Constants.ZOOM_SCALE_BASE) > Constants.ZOOM_SCALE_THRESHOLD) {
isZoomEnabled = scaleFactor > 1
updateZoomMode(isZoomEnabled)
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (abs(scaleFactor - Constants.ZOOM_SCALE_BASE) > Constants.ZOOM_SCALE_THRESHOLD) {
isZoomEnabled = scaleFactor > 1
updateZoomMode(isZoomEnabled)
}
return true
}
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
}).apply { isQuickScaleEnabled = false }
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
},
).apply { isQuickScaleEnabled = false }
init {
@Suppress("ClickableViewAccessibility")
@@ -8,8 +8,8 @@ import androidx.core.view.isVisible
import com.google.android.exoplayer2.ui.PlayerView
import org.jellyfin.mobile.databinding.FragmentPlayerBinding
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.isAutoRotateOn
import org.jellyfin.mobile.utils.extensions.lockOrientation
import org.jellyfin.mobile.utils.isAutoRotateOn
class PlayerLockScreenHelper(
private val playerFragment: PlayerFragment,
@@ -189,7 +189,13 @@ class PlayerMenus(
setOnDismissListener(this@PlayerMenus)
}
private fun buildMenuItems(menu: Menu, groupId: Int, mediaStreams: List<MediaStream>, selectedStream: MediaStream?, showNone: Boolean = false) {
private fun buildMenuItems(
menu: Menu,
groupId: Int,
mediaStreams: List<MediaStream>,
selectedStream: MediaStream?,
showNone: Boolean = false,
) {
menu.clear()
val itemNone = if (showNone) menu.add(groupId, -1, Menu.NONE, fragment.getString(R.string.menu_item_none)) else null
val menuItems = mediaStreams.map { mediaStream ->
@@ -16,14 +16,14 @@ import de.Maxr1998.modernpreferences.helpers.screen
import de.Maxr1998.modernpreferences.helpers.singleChoice
import de.Maxr1998.modernpreferences.preferences.CheckBoxPreference
import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.R
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.databinding.FragmentSettingsBinding
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.applyWindowInsetsAsMargins
import org.jellyfin.mobile.utils.extensions.requireMainActivity
import org.jellyfin.mobile.utils.getDownloadsPaths
import org.jellyfin.mobile.utils.isPackageInstalled
import org.jellyfin.mobile.utils.extensions.requireMainActivity
import org.jellyfin.mobile.utils.withThemedContext
import org.koin.android.ext.android.inject
@@ -22,12 +22,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import org.jellyfin.mobile.MainViewModel
import org.jellyfin.mobile.R
import org.jellyfin.mobile.app.ApiClientController
import org.jellyfin.mobile.databinding.FragmentConnectBinding
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.applyWindowInsetsAsMargins
import org.jellyfin.mobile.MainViewModel
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.discovery.LocalServerDiscovery
import org.jellyfin.sdk.discovery.RecommendedServerInfo
@@ -24,11 +24,11 @@ import androidx.core.content.getSystemService
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.BuildConfig
import org.jellyfin.mobile.R
import org.jellyfin.mobile.webapp.WebViewFragment
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.settings.ExternalPlayerPackage
import org.jellyfin.mobile.webapp.WebViewFragment
import org.koin.android.ext.android.get
import timber.log.Timber
import java.io.File
@@ -36,29 +36,29 @@ import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.launch
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.R
import org.jellyfin.mobile.app.ApiClientController
import org.jellyfin.mobile.app.AppPreferences
import org.jellyfin.mobile.bridge.ExternalPlayer
import org.jellyfin.mobile.bridge.NativeInterface
import org.jellyfin.mobile.bridge.NativePlayer
import org.jellyfin.mobile.bridge.NativePlayerHost
import org.jellyfin.mobile.player.interaction.PlayOptions
import org.jellyfin.mobile.app.ApiClientController
import org.jellyfin.mobile.databinding.FragmentWebviewBinding
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.data.entity.ServerEntity
import org.jellyfin.mobile.databinding.FragmentWebviewBinding
import org.jellyfin.mobile.player.interaction.PlayOptions
import org.jellyfin.mobile.player.ui.PlayerFragment
import org.jellyfin.mobile.setup.ConnectFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.FRAGMENT_WEB_VIEW_EXTRA_SERVER
import org.jellyfin.mobile.utils.extensions.addFragment
import org.jellyfin.mobile.utils.applyDefault
import org.jellyfin.mobile.utils.applyWindowInsetsAsMargins
import org.jellyfin.mobile.utils.dip
import org.jellyfin.mobile.utils.extensions.addFragment
import org.jellyfin.mobile.utils.extensions.replaceFragment
import org.jellyfin.mobile.utils.fadeIn
import org.jellyfin.mobile.utils.initLocale
import org.jellyfin.mobile.utils.inject
import org.jellyfin.mobile.utils.isOutdated
import org.jellyfin.mobile.utils.extensions.replaceFragment
import org.jellyfin.mobile.utils.requestNoBatteryOptimizations
import org.jellyfin.mobile.utils.runOnUiThread
import org.json.JSONException
@@ -203,14 +203,22 @@ class WebViewFragment : Fragment(), NativePlayerHost {
}
}
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
override fun onReceivedHttpError(
view: WebView,
request: WebResourceRequest,
errorResponse: WebResourceResponse,
) {
val errorMessage = errorResponse.data?.run { bufferedReader().use(Reader::readText) }
Timber.e("Received WebView HTTP %d error: %s", errorResponse.statusCode, errorMessage)
if (request.url == Uri.parse(view.url)) onErrorReceived()
}
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceErrorCompat) {
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceErrorCompat,
) {
val description = if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION)) error.description else null
val errorCode = if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE)) error.errorCode else ERROR_UNKNOWN
Timber.e("Received WebView error %d at %s: %s", errorCode, request.url.toString(), description)