diff --git a/app/src/main/java/org/jellyfin/mobile/MainActivity.kt b/app/src/main/java/org/jellyfin/mobile/MainActivity.kt index e1bb5941..b2657f2f 100644 --- a/app/src/main/java/org/jellyfin/mobile/MainActivity.kt +++ b/app/src/main/java/org/jellyfin/mobile/MainActivity.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.os.IBinder import android.view.OrientationEventListener import androidx.appcompat.app.AppCompatActivity -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @@ -17,7 +16,10 @@ import org.jellyfin.mobile.cast.IChromecast import org.jellyfin.mobile.fragment.ConnectFragment import org.jellyfin.mobile.fragment.WebViewFragment import org.jellyfin.mobile.player.PlayerFragment -import org.jellyfin.mobile.utils.* +import org.jellyfin.mobile.utils.Constants +import org.jellyfin.mobile.utils.PermissionRequestHelper +import org.jellyfin.mobile.utils.SmartOrientationListener +import org.jellyfin.mobile.utils.replaceFragment import org.jellyfin.mobile.viewmodel.MainViewModel import org.jellyfin.mobile.viewmodel.ServerState import org.jellyfin.mobile.webapp.RemotePlayerService @@ -31,8 +33,6 @@ class MainActivity : AppCompatActivity() { val chromecast: IChromecast = Chromecast() private val permissionRequestHelper: PermissionRequestHelper by inject() - val rootView: CoordinatorLayout by lazyView(R.id.root_view) - var serviceBinder: RemotePlayerService.ServiceBinder? = null private set private val serviceConnection: ServiceConnection = object : ServiceConnection { diff --git a/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt b/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt index ce4cb7a7..9dc537b4 100644 --- a/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt +++ b/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt @@ -91,10 +91,10 @@ class WebViewFragment : Fragment() { // offsetting 100dp from the center in both directions // uses the maximum available space val verticalCenter = webView.measuredHeight / 2 - val offset = webView.context.dip(100) + val offset = webView.resources.dip(100) // Arbitrary, currently 2x minimum touch target size - val exclusionWidth = webView.context.dip(96) + val exclusionWidth = webView.resources.dip(96) webView.systemGestureExclusionRects = listOf( Rect( diff --git a/app/src/main/java/org/jellyfin/mobile/player/PlayerFragment.kt b/app/src/main/java/org/jellyfin/mobile/player/PlayerFragment.kt index e1487529..bcd3373a 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/PlayerFragment.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/PlayerFragment.kt @@ -282,7 +282,7 @@ class PlayerFragment : Fragment() { return false // Check whether swipe was started in excluded region - if (firstEvent.y < GESTURE_EXCLUSION_AREA_TOP * resources.displayMetrics.density) + if (firstEvent.y < resources.dip(GESTURE_EXCLUSION_AREA_TOP)) return false // Check whether swipe was oriented vertically diff --git a/app/src/main/java/org/jellyfin/mobile/utils/ActivityExtensions.kt b/app/src/main/java/org/jellyfin/mobile/utils/ActivityExtensions.kt new file mode 100644 index 00000000..27e53fa5 --- /dev/null +++ b/app/src/main/java/org/jellyfin/mobile/utils/ActivityExtensions.kt @@ -0,0 +1,47 @@ +@file:Suppress("DEPRECATION") + +package org.jellyfin.mobile.utils + +import android.app.Activity +import android.content.pm.ActivityInfo +import android.graphics.Point +import android.view.Surface +import android.view.View +import android.view.WindowManager + +const val STABLE_LAYOUT_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + +const val FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or + View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + +fun Activity.isFullscreen() = window.decorView.systemUiVisibility.hasFlag(FULLSCREEN_FLAGS) + +fun Activity.enableFullscreen() { + window.apply { + decorView.systemUiVisibility = FULLSCREEN_FLAGS + addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) + } +} + +fun Activity.disableFullscreen(keepStableLayout: Boolean = false) { + window.apply { + decorView.systemUiVisibility = if (keepStableLayout) STABLE_LAYOUT_FLAGS else 0 + clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } +} + +fun Activity.lockOrientation() { + val display = windowManager.defaultDisplay + val size = Point().also(display::getSize) + val height = size.y + val width = size.x + requestedOrientation = when (display.rotation) { + Surface.ROTATION_90 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + Surface.ROTATION_180 -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + Surface.ROTATION_270 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + else -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } +} diff --git a/app/src/main/java/org/jellyfin/mobile/utils/FragmentExtensions.kt b/app/src/main/java/org/jellyfin/mobile/utils/FragmentExtensions.kt new file mode 100644 index 00000000..8364d354 --- /dev/null +++ b/app/src/main/java/org/jellyfin/mobile/utils/FragmentExtensions.kt @@ -0,0 +1,24 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package org.jellyfin.mobile.utils + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.add +import androidx.fragment.app.replace +import org.jellyfin.mobile.MainActivity +import org.jellyfin.mobile.R + +inline fun FragmentManager.addFragment() { + beginTransaction().apply { + add(R.id.fragment_container) + addToBackStack(null) + }.commit() +} + +inline fun FragmentManager.replaceFragment(args: Bundle? = null) { + beginTransaction().replace(R.id.fragment_container, args = args).commit() +} + +inline fun Fragment.requireMainActivity(): MainActivity = requireActivity() as MainActivity diff --git a/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt b/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt index 9fc1b5cb..7b2c6fca 100644 --- a/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt +++ b/app/src/main/java/org/jellyfin/mobile/utils/SystemUtils.kt @@ -33,8 +33,7 @@ fun MainActivity.requestNoBatteryOptimizations() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val powerManager: PowerManager = getSystemService(AppCompatActivity.POWER_SERVICE) as PowerManager if (!appPreferences.ignoreBatteryOptimizations && !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) { - Snackbar.make(rootView, R.string.battery_optimizations_message, Snackbar.LENGTH_INDEFINITE).apply { - // Add action + Snackbar.make(findViewById(R.id.root_view), R.string.battery_optimizations_message, Snackbar.LENGTH_INDEFINITE).apply { setAction(android.R.string.ok) { try { val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS) @@ -46,7 +45,6 @@ fun MainActivity.requestNoBatteryOptimizations() { // Ignore after the user interacted with the snackbar at least once appPreferences.ignoreBatteryOptimizations = true } - show() } } diff --git a/app/src/main/java/org/jellyfin/mobile/utils/UIExtensions.kt b/app/src/main/java/org/jellyfin/mobile/utils/UIExtensions.kt index bc90c8de..de98a5d6 100644 --- a/app/src/main/java/org/jellyfin/mobile/utils/UIExtensions.kt +++ b/app/src/main/java/org/jellyfin/mobile/utils/UIExtensions.kt @@ -1,75 +1,23 @@ -@file:Suppress("NOTHING_TO_INLINE") +@file:Suppress("unused", "NOTHING_TO_INLINE") package org.jellyfin.mobile.utils -import android.app.Activity import android.content.Context -import android.content.pm.ActivityInfo -import android.graphics.Point -import android.os.Bundle -import android.view.* +import android.content.res.Resources +import android.view.ContextThemeWrapper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.Toast -import androidx.annotation.IdRes import androidx.annotation.StringRes import androidx.annotation.StyleRes import androidx.core.view.ViewCompat import androidx.core.view.updateMargins -import androidx.fragment.app.* import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.jellyfin.mobile.MainActivity -import org.jellyfin.mobile.R - -inline fun Activity.lazyView(@IdRes id: Int) = - lazy(LazyThreadSafetyMode.NONE) { findViewById(id) } - -@Suppress("DEPRECATION") -const val STABLE_LAYOUT_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - -@Suppress("DEPRECATION") -const val FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - -@Suppress("DEPRECATION") -fun Activity.isFullscreen() = window.decorView.systemUiVisibility.hasFlag(FULLSCREEN_FLAGS) - -@Suppress("DEPRECATION") -fun Activity.enableFullscreen() { - window.apply { - decorView.systemUiVisibility = FULLSCREEN_FLAGS - addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) - } -} - -@Suppress("DEPRECATION") -fun Activity.disableFullscreen(keepStableLayout: Boolean = false) { - window.apply { - decorView.systemUiVisibility = if (keepStableLayout) STABLE_LAYOUT_FLAGS else 0 - clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - } -} - -@Suppress("DEPRECATION") -fun Activity.lockOrientation() { - val display = windowManager.defaultDisplay - val size = Point().also(display::getSize) - val height = size.y - val width = size.x - requestedOrientation = when (display.rotation) { - Surface.ROTATION_90 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT - Surface.ROTATION_180 -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - Surface.ROTATION_270 -> if (width > height) ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - else -> if (height > width) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - } -} - -inline fun Context.dip(px: Int) = (px * resources.displayMetrics.density).toInt() inline fun Context.toast(@StringRes text: Int, duration: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, text, duration).show() @@ -81,24 +29,10 @@ inline fun LifecycleOwner.runOnUiThread(noinline block: suspend CoroutineScope.( lifecycleScope.launch(Dispatchers.Main, block = block) } -inline fun FragmentManager.addFragment() { - beginTransaction().apply { - add(R.id.fragment_container) - addToBackStack(null) - }.commit() -} - -inline fun FragmentManager.replaceFragment(args: Bundle? = null) { - beginTransaction().replace(R.id.fragment_container, args = args).commit() -} - fun LayoutInflater.withThemedContext(context: Context, @StyleRes style: Int): LayoutInflater { return cloneInContext(ContextThemeWrapper(context, style)) } -@Suppress("NOTHING_TO_INLINE") -inline fun Fragment.requireMainActivity(): MainActivity = requireActivity() as MainActivity - fun View.applyWindowInsetsAsMargins() { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val layoutParams = layoutParams as ViewGroup.MarginLayoutParams @@ -111,3 +45,5 @@ fun View.applyWindowInsetsAsMargins() { insets } } + +inline fun Resources.dip(px: Int) = (px * displayMetrics.density).toInt()