Integrated JWPlayer
implemented playerView on both fragments theme 1 and theme 2 Full screen activity LiveStreamPlayerActivity
This commit is contained in:
@@ -26,6 +26,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.bumptech.glide.Glide
|
||||
import com.jwplayer.pub.api.license.LicenseUtil
|
||||
import com.woka.BuildConfig
|
||||
import com.woka.R
|
||||
import com.woka.WokaApp.Companion.userPrefs
|
||||
@@ -128,6 +129,8 @@ class HomeActivity : WokaBaseActivity(),
|
||||
if (userPrefs?.userLiveData?.isInitialized == false){
|
||||
userPrefs?.loadUserData()
|
||||
}
|
||||
|
||||
LicenseUtil().setLicenseKey(this, "LkYoNusv+gSIVJIrXa5Bf59iBNlUMxeg82PM/l8JWk+cD4BE")
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.woka.home.fragments
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -8,16 +7,17 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_TIME_TICK
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.woka.R
|
||||
import com.woka.WokaApp.Companion.userPrefs
|
||||
import com.woka.databinding.FragmentHome1Binding
|
||||
@@ -25,11 +25,13 @@ import com.woka.home.HomeViewModel
|
||||
import com.woka.home.TimePeriod
|
||||
import com.woka.mvvm.userDataModels.UserDataResponse
|
||||
import com.woka.networking.ApiResult
|
||||
import com.woka.players.LiveStreamPlayerActivity
|
||||
import com.woka.utils.UserType
|
||||
import com.woka.utils.hide
|
||||
import com.woka.utils.scaleAnimate
|
||||
import com.woka.utils.show
|
||||
|
||||
|
||||
class Home1Fragment : Fragment() {
|
||||
|
||||
private lateinit var binding: FragmentHome1Binding
|
||||
@@ -44,6 +46,8 @@ class Home1Fragment : Fragment() {
|
||||
private var star1Animator: ValueAnimator? = null
|
||||
private var star2Animator: ValueAnimator? = null
|
||||
|
||||
private lateinit var player: ExoPlayer
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -51,8 +55,11 @@ class Home1Fragment : Fragment() {
|
||||
binding = FragmentHome1Binding.inflate(inflater, container, false)
|
||||
activity?.let {
|
||||
viewModel = ViewModelProvider(it)[HomeViewModel::class.java]
|
||||
player = ExoPlayer.Builder(it).build()
|
||||
}
|
||||
|
||||
initPlayerView()
|
||||
|
||||
handleScaleAnimations()
|
||||
|
||||
updateBackground()
|
||||
@@ -77,6 +84,9 @@ class Home1Fragment : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
handleAnimations()
|
||||
if (!player.isPlaying){
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -86,6 +96,26 @@ class Home1Fragment : Fragment() {
|
||||
if (cloud2Animator?.isRunning == true) cloud2Animator?.pause()
|
||||
if (star1Animator?.isRunning == true) star1Animator?.pause()
|
||||
if (star2Animator?.isRunning == true) star2Animator?.pause()
|
||||
|
||||
if (player.isPlaying) player.pause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
player.release()
|
||||
}
|
||||
|
||||
private fun initPlayerView() {
|
||||
binding.playerView.player = player
|
||||
|
||||
binding.playerView.useController = false
|
||||
player.volume = 0f
|
||||
|
||||
val videoUri = Uri.parse("https://d3volyx7jx7oal.cloudfront.net/master.m3u8")
|
||||
val mediaItem = MediaItem.fromUri(videoUri)
|
||||
player.setMediaItem(mediaItem)
|
||||
player.playWhenReady = true
|
||||
player.prepare()
|
||||
}
|
||||
|
||||
private fun setObservers() {
|
||||
@@ -114,7 +144,11 @@ class Home1Fragment : Fragment() {
|
||||
|
||||
private fun clickEvents() {
|
||||
binding.apply {
|
||||
|
||||
blackImage.setOnClickListener {
|
||||
activity?.let {
|
||||
startActivity(Intent(it, LiveStreamPlayerActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +169,7 @@ class Home1Fragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleScaleAnimations(){
|
||||
private fun handleScaleAnimations() {
|
||||
binding.apply {
|
||||
webSeriesLl.post {
|
||||
webSeriesLl.scaleAnimate()
|
||||
@@ -164,67 +198,69 @@ class Home1Fragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun handleAnimations() {
|
||||
if (tvAnimator == null) {
|
||||
binding.tvView.post {
|
||||
val endMargin: Float =
|
||||
25f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
tvAnimator = ObjectAnimator.ofFloat(
|
||||
binding.tvView,
|
||||
"translationX",
|
||||
resources.displayMetrics.widthPixels - binding.tvView.width - (2 * endMargin)
|
||||
).apply {
|
||||
duration = 12000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
synchronized(this){
|
||||
if (tvAnimator == null) {
|
||||
binding.tvView.post {
|
||||
val endMargin: Float =
|
||||
25f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
tvAnimator = ObjectAnimator.ofFloat(
|
||||
binding.tvView,
|
||||
"translationX",
|
||||
resources.displayMetrics.widthPixels - binding.tvView.width - (2 * endMargin)
|
||||
).apply {
|
||||
duration = 12000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
}
|
||||
}
|
||||
} else if (tvAnimator?.isPaused == true) {
|
||||
tvAnimator?.resume()
|
||||
}
|
||||
} else if (tvAnimator?.isPaused == true){
|
||||
tvAnimator?.resume()
|
||||
}
|
||||
|
||||
if (cloud1Animator == null) {
|
||||
binding.cloud1.post {
|
||||
val cloud1Width: Float =
|
||||
900f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
cloud1Animator = ObjectAnimator.ofFloat(
|
||||
binding.cloud1,
|
||||
"translationX",
|
||||
-cloud1Width + resources.displayMetrics.widthPixels
|
||||
).apply {
|
||||
duration = 120_000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
if (cloud1Animator == null) {
|
||||
binding.cloud1.post {
|
||||
val cloud1Width: Float =
|
||||
900f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
cloud1Animator = ObjectAnimator.ofFloat(
|
||||
binding.cloud1,
|
||||
"translationX",
|
||||
-cloud1Width + resources.displayMetrics.widthPixels
|
||||
).apply {
|
||||
duration = 120_000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
}
|
||||
}
|
||||
} else if (cloud1Animator?.isPaused == true) {
|
||||
cloud1Animator?.resume()
|
||||
}
|
||||
} else if (cloud1Animator?.isPaused == true){
|
||||
cloud1Animator?.resume()
|
||||
}
|
||||
|
||||
if (cloud2Animator == null) {
|
||||
binding.cloud2.post {
|
||||
val cloud2Width: Float =
|
||||
900f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
cloud2Animator = ObjectAnimator.ofFloat(
|
||||
binding.cloud2,
|
||||
"translationX",
|
||||
cloud2Width - resources.displayMetrics.widthPixels
|
||||
).apply {
|
||||
duration = 120_000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
if (cloud2Animator == null) {
|
||||
binding.cloud2.post {
|
||||
val cloud2Width: Float =
|
||||
900f * (resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
cloud2Animator = ObjectAnimator.ofFloat(
|
||||
binding.cloud2,
|
||||
"translationX",
|
||||
cloud2Width - resources.displayMetrics.widthPixels
|
||||
).apply {
|
||||
duration = 120_000
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
start()
|
||||
}
|
||||
}
|
||||
} else if (cloud2Animator?.isPaused == true) {
|
||||
cloud2Animator?.resume()
|
||||
}
|
||||
} else if (cloud2Animator?.isPaused == true) {
|
||||
cloud2Animator?.resume()
|
||||
}
|
||||
|
||||
handleNightAnimations()
|
||||
handleNightAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNightAnimations(){
|
||||
private fun handleNightAnimations() {
|
||||
if (currentBackground == TimePeriod.NIGHT && star1Animator == null) {
|
||||
binding.star1.post {
|
||||
star1Animator = ObjectAnimator.ofFloat(binding.star1, "alpha", 0.3f, 1f).apply {
|
||||
@@ -234,7 +270,7 @@ class Home1Fragment : Fragment() {
|
||||
start()
|
||||
}
|
||||
}
|
||||
} else if (star1Animator?.isPaused == true){
|
||||
} else if (star1Animator?.isPaused == true) {
|
||||
star1Animator?.resume()
|
||||
}
|
||||
|
||||
@@ -338,7 +374,9 @@ class Home1Fragment : Fragment() {
|
||||
binding.moon.show()
|
||||
|
||||
currentBackground = timePeriod
|
||||
handleNightAnimations()
|
||||
synchronized(this){
|
||||
handleNightAnimations()
|
||||
}
|
||||
|
||||
binding.grass.setImageResource(R.drawable.img_grass_n)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package com.woka.home.fragments
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.woka.R
|
||||
import com.woka.WokaApp
|
||||
import com.woka.databinding.FragmentHome2Binding
|
||||
import com.woka.home.HomeViewModel
|
||||
import com.woka.mvvm.userDataModels.UserDataResponse
|
||||
import com.woka.networking.ApiResult
|
||||
import com.woka.players.LiveStreamPlayerActivity
|
||||
import com.woka.utils.UserType
|
||||
import com.woka.utils.toast
|
||||
|
||||
@@ -21,6 +26,8 @@ class Home2Fragment : Fragment() {
|
||||
|
||||
private lateinit var viewModel: HomeViewModel
|
||||
|
||||
private lateinit var player: ExoPlayer
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -29,8 +36,11 @@ class Home2Fragment : Fragment() {
|
||||
|
||||
activity?.let {
|
||||
viewModel = ViewModelProvider(it)[HomeViewModel::class.java]
|
||||
player = ExoPlayer.Builder(it).build()
|
||||
}
|
||||
|
||||
initPlayerView()
|
||||
|
||||
setObservers()
|
||||
|
||||
clickEvents()
|
||||
@@ -38,10 +48,41 @@ class Home2Fragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!player.isPlaying){
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (player.isPlaying) player.pause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
player.release()
|
||||
}
|
||||
|
||||
private fun initPlayerView() {
|
||||
binding.playerView.player = player
|
||||
|
||||
binding.playerView.useController = false
|
||||
player.volume = 0f
|
||||
|
||||
val videoUri = Uri.parse("https://d3volyx7jx7oal.cloudfront.net/master.m3u8")
|
||||
val mediaItem = MediaItem.fromUri(videoUri)
|
||||
player.setMediaItem(mediaItem)
|
||||
player.playWhenReady = true
|
||||
|
||||
player.prepare()
|
||||
}
|
||||
|
||||
private fun clickEvents(){
|
||||
binding.apply {
|
||||
webSeries.setOnClickListener {
|
||||
toast("toast")
|
||||
playerCard.setOnClickListener {
|
||||
startActivity(Intent(activity, LiveStreamPlayerActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
87
app/src/main/java/com/woka/players/KeepScreenOnHandler.kt
Normal file
87
app/src/main/java/com/woka/players/KeepScreenOnHandler.kt
Normal file
@@ -0,0 +1,87 @@
|
||||
package com.woka.players
|
||||
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import com.jwplayer.pub.api.JWPlayer
|
||||
import com.jwplayer.pub.api.events.AdCompleteEvent
|
||||
import com.jwplayer.pub.api.events.AdErrorEvent
|
||||
import com.jwplayer.pub.api.events.AdPauseEvent
|
||||
import com.jwplayer.pub.api.events.AdPlayEvent
|
||||
import com.jwplayer.pub.api.events.AdSkippedEvent
|
||||
import com.jwplayer.pub.api.events.CompleteEvent
|
||||
import com.jwplayer.pub.api.events.ErrorEvent
|
||||
import com.jwplayer.pub.api.events.EventType
|
||||
import com.jwplayer.pub.api.events.PauseEvent
|
||||
import com.jwplayer.pub.api.events.PlayEvent
|
||||
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents.OnAdCompleteListener
|
||||
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents.OnAdErrorListener
|
||||
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents.OnAdPauseListener
|
||||
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents.OnAdPlayListener
|
||||
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents.OnAdSkippedListener
|
||||
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents
|
||||
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents.OnPauseListener
|
||||
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents.OnPlayListener
|
||||
|
||||
class KeepScreenOnHandler(player: JWPlayer, window: Window) : OnPlayListener,
|
||||
OnPauseListener, VideoPlayerEvents.OnCompleteListener, VideoPlayerEvents.OnErrorListener,
|
||||
OnAdPlayListener, OnAdPauseListener, OnAdCompleteListener, OnAdSkippedListener,
|
||||
OnAdErrorListener {
|
||||
|
||||
private val mWindow: Window
|
||||
|
||||
init {
|
||||
player.addListener(EventType.PLAY, this)
|
||||
player.addListener(EventType.PAUSE, this)
|
||||
player.addListener(EventType.COMPLETE, this)
|
||||
player.addListener(EventType.ERROR, this)
|
||||
player.addListener(EventType.AD_PLAY, this)
|
||||
player.addListener(EventType.AD_PAUSE, this)
|
||||
player.addListener(EventType.AD_COMPLETE, this)
|
||||
player.addListener(EventType.AD_ERROR, this)
|
||||
mWindow = window
|
||||
}
|
||||
|
||||
private fun updateWakeLock(enable: Boolean) {
|
||||
if (enable) {
|
||||
mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(errorEvent: ErrorEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onAdPlay(adPlayEvent: AdPlayEvent) {
|
||||
updateWakeLock(true)
|
||||
}
|
||||
|
||||
override fun onAdPause(adPauseEvent: AdPauseEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onAdComplete(adCompleteEvent: AdCompleteEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onAdSkipped(adSkippedEvent: AdSkippedEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onAdError(adErrorEvent: AdErrorEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onComplete(completeEvent: CompleteEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onPause(pauseEvent: PauseEvent) {
|
||||
updateWakeLock(false)
|
||||
}
|
||||
|
||||
override fun onPlay(playEvent: PlayEvent) {
|
||||
updateWakeLock(true)
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/com/woka/players/LiveStreamPlayerActivity.kt
Normal file
102
app/src/main/java/com/woka/players/LiveStreamPlayerActivity.kt
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.woka.players
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.jwplayer.pub.api.JWPlayer
|
||||
import com.jwplayer.pub.api.configuration.PlayerConfig
|
||||
import com.jwplayer.pub.api.events.EventType
|
||||
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents
|
||||
import com.jwplayer.pub.api.fullscreen.FullscreenHandler
|
||||
import com.jwplayer.pub.api.media.playlists.PlaylistItem
|
||||
import com.woka.databinding.ActivityLiveStreamPlayerBinding
|
||||
|
||||
class LiveStreamPlayerActivity : AppCompatActivity(), FullscreenHandler {
|
||||
|
||||
companion object{
|
||||
private const val TAG = "LiveStreamPlayerActivity_tag"
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityLiveStreamPlayerBinding
|
||||
|
||||
private lateinit var player: JWPlayer
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
binding = ActivityLiveStreamPlayerBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
val windowInsetsController =
|
||||
WindowCompat.getInsetsController(window, window.decorView)
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
setUpPlayer()
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
player.stop()
|
||||
}
|
||||
|
||||
private fun setUpPlayer() {
|
||||
player = binding.playerView.player
|
||||
|
||||
player.setFullscreenHandler(this)
|
||||
|
||||
player.setFullscreen(true, false)
|
||||
|
||||
player.addListener(EventType.ERROR, VideoPlayerEvents.OnErrorListener {
|
||||
Log.d(TAG, "onError: ${it.errorCode}")
|
||||
Log.d(TAG, "onError: ${it.exception}")
|
||||
Log.d(TAG, "onError: ${it.message}")
|
||||
})
|
||||
|
||||
player.addListener(EventType.SETUP_ERROR, VideoPlayerEvents.OnSetupErrorListener {
|
||||
Log.d(TAG, "setUpPlayer: $it")
|
||||
})
|
||||
|
||||
// to keep up the screen om when video is being played
|
||||
KeepScreenOnHandler(player, window)
|
||||
|
||||
val playlistItem = PlaylistItem.Builder()
|
||||
.file("https://d3volyx7jx7oal.cloudfront.net/master.m3u8")
|
||||
.mediaId("YR5pnlIM")
|
||||
.build()
|
||||
val playlist: MutableList<PlaylistItem> = ArrayList()
|
||||
playlist.add(playlistItem)
|
||||
|
||||
val config =
|
||||
PlayerConfig.Builder()
|
||||
.playlist(playlist)
|
||||
.build()
|
||||
player.setup(config)
|
||||
player.play()
|
||||
}
|
||||
|
||||
override fun onFullscreenRequested() {}
|
||||
|
||||
override fun onFullscreenExitRequested() {
|
||||
player.stop()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onAllowRotationChanged(allowRotation: Boolean) {}
|
||||
|
||||
override fun updateLayoutParams(layoutParams: ViewGroup.LayoutParams?) {}
|
||||
|
||||
override fun setUseFullscreenLayoutFlags(flags: Boolean) {}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user