2022-01-07 19:27:25 +00:00
package com.lagradost.cloudstream3
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.Build
2022-04-03 15:00:50 +00:00
import android.util.Log
2022-01-07 19:27:25 +00:00
import android.view.*
import android.widget.TextView
import android.widget.Toast
2022-08-08 12:37:46 +00:00
import androidx.annotation.MainThread
2022-01-07 19:27:25 +00:00
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastSession
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType
2022-08-07 23:03:54 +00:00
import com.lagradost.cloudstream3.ui.result.UiText
2022-08-28 23:52:15 +00:00
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
import com.lagradost.cloudstream3.utils.UIHelper.toPx
2022-07-27 23:38:20 +00:00
import org.schabi.newpipe.extractor.NewPipe
2022-01-07 19:27:25 +00:00
import java.util.*
object CommonActivity {
2022-08-08 12:37:46 +00:00
@MainThread
2022-01-07 19:27:25 +00:00
fun Activity ?. getCastSession ( ) : CastSession ? {
return ( this as MainActivity ? ) ?. mSessionManager ?. currentCastSession
}
var canEnterPipMode : Boolean = false
var canShowPipMode : Boolean = false
var isInPIPMode : Boolean = false
val onColorSelectedEvent = Event < Pair < Int , Int > > ( )
val onDialogDismissedEvent = Event < Int > ( )
var playerEventListener : ( ( PlayerEventType ) -> Unit ) ? = null
2022-01-18 00:24:23 +00:00
var keyEventListener : ( ( Pair < KeyEvent ? , Boolean > ) -> Boolean ) ? = null
2022-01-07 19:27:25 +00:00
var currentToast : Toast ? = null
2022-08-07 23:03:54 +00:00
fun showToast ( act : Activity ? , text : UiText , duration : Int ) {
if ( act == null ) return
text . asStringNull ( act ) ?. let {
showToast ( act , it , duration )
}
}
2022-01-07 19:27:25 +00:00
fun showToast ( act : Activity ? , @StringRes message : Int , duration : Int ) {
if ( act == null ) return
showToast ( act , act . getString ( message ) , duration )
}
2022-04-03 15:00:50 +00:00
const val TAG = " COMPACT "
2022-01-07 19:27:25 +00:00
/** duration is Toast.LENGTH_SHORT if null*/
fun showToast ( act : Activity ? , message : String ? , duration : Int ? = null ) {
2022-04-03 15:00:50 +00:00
if ( act == null || message == null ) {
Log . w ( TAG , " invalid showToast act = $act message = $message " )
return
}
Log . i ( TAG , " showToast = $message " )
2022-01-07 19:27:25 +00:00
try {
currentToast ?. cancel ( )
} catch ( e : Exception ) {
2022-04-03 15:00:50 +00:00
logError ( e )
2022-01-07 19:27:25 +00:00
}
try {
val inflater =
act . getSystemService ( AppCompatActivity . LAYOUT _INFLATER _SERVICE ) as LayoutInflater
val layout : View = inflater . inflate (
R . layout . toast ,
act . findViewById < View > ( R . id . toast _layout _root ) as ViewGroup ?
)
val text = layout . findViewById ( R . id . text ) as TextView
text . text = message . trim ( )
val toast = Toast ( act )
toast . setGravity ( Gravity . CENTER _HORIZONTAL or Gravity . BOTTOM , 0 , 5. toPx )
toast . duration = duration ?: Toast . LENGTH _SHORT
toast . view = layout
2022-04-06 15:16:08 +00:00
//https://github.com/PureWriter/ToastCompat
2022-01-07 19:27:25 +00:00
toast . show ( )
currentToast = toast
} catch ( e : Exception ) {
2022-04-03 15:00:50 +00:00
logError ( e )
2022-01-07 19:27:25 +00:00
}
}
fun setLocale ( context : Context ? , languageCode : String ? ) {
if ( context == null || languageCode == null ) return
val locale = Locale ( languageCode )
val resources : Resources = context . resources
val config = resources . configuration
Locale . setDefault ( locale )
config . setLocale ( locale )
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . N )
context . createConfigurationContext ( config )
resources . updateConfiguration ( config , resources . displayMetrics )
}
fun Context . updateLocale ( ) {
val settingsManager = PreferenceManager . getDefaultSharedPreferences ( this )
val localeCode = settingsManager . getString ( getString ( R . string . locale _key ) , null )
setLocale ( this , localeCode )
}
fun init ( act : Activity ? ) {
if ( act == null ) return
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
//https://developer.android.com/guide/topics/ui/picture-in-picture
canShowPipMode =
2022-04-10 13:57:02 +00:00
Build . VERSION . SDK _INT >= Build . VERSION_CODES . N && // OS SUPPORT
2022-01-07 19:27:25 +00:00
act . packageManager . hasSystemFeature ( PackageManager . FEATURE _PICTURE _IN _PICTURE ) && // HAS FEATURE, MIGHT BE BLOCKED DUE TO POWER DRAIN
act . hasPIPPermission ( ) // CHECK IF FEATURE IS ENABLED IN SETTINGS
act . updateLocale ( )
2022-08-28 23:52:15 +00:00
act . updateTv ( )
2022-07-27 23:38:20 +00:00
NewPipe . init ( DownloaderTestImpl . getInstance ( ) )
2022-01-07 19:27:25 +00:00
}
private fun Activity . enterPIPMode ( ) {
if ( ! shouldShowPIPMode ( canEnterPipMode ) || ! canShowPipMode ) return
try {
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . O ) {
try {
enterPictureInPictureMode ( PictureInPictureParams . Builder ( ) . build ( ) )
} catch ( e : Exception ) {
enterPictureInPictureMode ( )
}
} else {
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . N ) {
enterPictureInPictureMode ( )
}
}
} catch ( e : Exception ) {
logError ( e )
}
}
fun onUserLeaveHint ( act : Activity ? ) {
if ( canEnterPipMode && canShowPipMode ) {
act ?. enterPIPMode ( )
}
}
2022-01-18 00:24:23 +00:00
fun loadThemes ( act : Activity ? ) {
if ( act == null ) return
2022-01-07 19:27:25 +00:00
val settingsManager = PreferenceManager . getDefaultSharedPreferences ( act )
val currentTheme =
when ( settingsManager . getString ( act . getString ( R . string . app _theme _key ) , " AmoledLight " ) ) {
" Black " -> R . style . AppTheme
" Light " -> R . style . LightMode
" Amoled " -> R . style . AmoledMode
" AmoledLight " -> R . style . AmoledModeLight
else -> R . style . AppTheme
}
val currentOverlayTheme =
when ( settingsManager . getString ( act . getString ( R . string . primary _color _key ) , " Normal " ) ) {
" Normal " -> R . style . OverlayPrimaryColorNormal
2022-04-09 21:07:52 +00:00
" CarnationPink " -> R . style . OverlayPrimaryColorCarnationPink
" DarkGreen " -> R . style . OverlayPrimaryColorDarkGreen
" Maroon " -> R . style . OverlayPrimaryColorMaroon
" NavyBlue " -> R . style . OverlayPrimaryColorNavyBlue
" Grey " -> R . style . OverlayPrimaryColorGrey
" White " -> R . style . OverlayPrimaryColorWhite
" Brown " -> R . style . OverlayPrimaryColorBrown
2022-01-07 19:27:25 +00:00
" Purple " -> R . style . OverlayPrimaryColorPurple
" Green " -> R . style . OverlayPrimaryColorGreen
" GreenApple " -> R . style . OverlayPrimaryColorGreenApple
" Red " -> R . style . OverlayPrimaryColorRed
" Banana " -> R . style . OverlayPrimaryColorBanana
" Party " -> R . style . OverlayPrimaryColorParty
" Pink " -> R . style . OverlayPrimaryColorPink
else -> R . style . OverlayPrimaryColorNormal
}
act . theme . applyStyle ( currentTheme , true )
act . theme . applyStyle ( currentOverlayTheme , true )
act . theme . applyStyle (
R . style . LoadedStyle ,
true
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
}
private fun getNextFocus (
act : Activity ? ,
view : View ? ,
direction : FocusDirection ,
depth : Int = 0
) : Int ? {
if ( view == null || depth >= 10 || act == null ) {
return null
}
val nextId = when ( direction ) {
FocusDirection . Left -> {
view . nextFocusLeftId
}
FocusDirection . Up -> {
view . nextFocusUpId
}
FocusDirection . Right -> {
view . nextFocusRightId
}
FocusDirection . Down -> {
view . nextFocusDownId
}
}
return if ( nextId != - 1 ) {
val next = act . findViewById < View ? > ( nextId )
//println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" )
if ( next ?. isShown == false ) {
getNextFocus ( act , next , direction , depth + 1 )
} else {
if ( depth == 0 ) {
null
} else {
nextId
}
}
} else {
null
}
}
enum class FocusDirection {
Left ,
Right ,
Up ,
Down ,
}
fun onKeyDown ( act : Activity ? , keyCode : Int , event : KeyEvent ? ) {
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
// Tested keycodes on remote:
// KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
// KeyEvent.KEYCODE_MEDIA_REWIND
// KeyEvent.KEYCODE_MENU
// KeyEvent.KEYCODE_MEDIA_NEXT
// KeyEvent.KEYCODE_MEDIA_PREVIOUS
// KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
// 149 keycode_numpad 5
when ( keyCode ) {
KeyEvent . KEYCODE _FORWARD , KeyEvent . KEYCODE _D , KeyEvent . KEYCODE _MEDIA _SKIP _FORWARD , KeyEvent . KEYCODE _MEDIA _FAST _FORWARD -> {
PlayerEventType . SeekForward
}
KeyEvent . KEYCODE _A , KeyEvent . KEYCODE _MEDIA _SKIP _BACKWARD , KeyEvent . KEYCODE _MEDIA _REWIND -> {
PlayerEventType . SeekBack
}
2022-08-05 23:41:35 +00:00
KeyEvent . KEYCODE _MEDIA _NEXT , KeyEvent . KEYCODE _BUTTON _R1 , KeyEvent . KEYCODE _N -> {
2022-01-07 19:27:25 +00:00
PlayerEventType . NextEpisode
}
2022-08-05 23:41:35 +00:00
KeyEvent . KEYCODE _MEDIA _PREVIOUS , KeyEvent . KEYCODE _BUTTON _L1 , KeyEvent . KEYCODE _B -> {
2022-01-07 19:27:25 +00:00
PlayerEventType . PrevEpisode
}
KeyEvent . KEYCODE _MEDIA _PAUSE -> {
PlayerEventType . Pause
}
KeyEvent . KEYCODE _MEDIA _PLAY , KeyEvent . KEYCODE _BUTTON _START -> {
PlayerEventType . Play
}
KeyEvent . KEYCODE _L , KeyEvent . KEYCODE _NUMPAD _7 -> {
PlayerEventType . Lock
}
KeyEvent . KEYCODE _H , KeyEvent . KEYCODE _MENU -> {
PlayerEventType . ToggleHide
}
KeyEvent . KEYCODE _M , KeyEvent . KEYCODE _VOLUME _MUTE -> {
PlayerEventType . ToggleMute
}
KeyEvent . KEYCODE _S , KeyEvent . KEYCODE _NUMPAD _9 -> {
PlayerEventType . ShowMirrors
}
2022-06-08 18:15:24 +00:00
// OpenSubtitles shortcut
KeyEvent . KEYCODE _O , KeyEvent . KEYCODE _NUMPAD _8 -> {
PlayerEventType . SearchSubtitlesOnline
}
2022-01-07 19:27:25 +00:00
KeyEvent . KEYCODE _E , KeyEvent . KEYCODE _NUMPAD _3 -> {
PlayerEventType . ShowSpeed
}
KeyEvent . KEYCODE _R , KeyEvent . KEYCODE _NUMPAD _0 -> {
PlayerEventType . Resize
}
2022-08-05 23:41:35 +00:00
KeyEvent . KEYCODE _C , KeyEvent . KEYCODE _NUMPAD _4 -> {
PlayerEventType . SkipOp
}
2022-01-07 19:27:25 +00:00
KeyEvent . KEYCODE _MEDIA _PLAY _PAUSE , KeyEvent . KEYCODE _P , KeyEvent . KEYCODE _SPACE , KeyEvent . KEYCODE _NUMPAD _ENTER , KeyEvent . KEYCODE _ENTER -> { // space is not captured due to navigation
PlayerEventType . PlayPauseToggle
}
else -> null
} ?. let { playerEvent ->
playerEventListener ?. invoke ( playerEvent )
}
//when (keyCode) {
// KeyEvent.KEYCODE_DPAD_CENTER -> {
// println("DPAD PRESSED")
// }
//}
}
fun dispatchKeyEvent ( act : Activity ? , event : KeyEvent ? ) : Boolean ? {
if ( act == null ) return null
event ?. keyCode ?. let { keyCode ->
when ( event . action ) {
KeyEvent . ACTION _DOWN -> {
if ( act . currentFocus != null ) {
val next = when ( keyCode ) {
KeyEvent . KEYCODE _DPAD _LEFT -> getNextFocus (
act ,
act . currentFocus ,
FocusDirection . Left
)
KeyEvent . KEYCODE _DPAD _RIGHT -> getNextFocus (
act ,
act . currentFocus ,
FocusDirection . Right
)
KeyEvent . KEYCODE _DPAD _UP -> getNextFocus (
act ,
act . currentFocus ,
FocusDirection . Up
)
KeyEvent . KEYCODE _DPAD _DOWN -> getNextFocus (
act ,
act . currentFocus ,
FocusDirection . Down
)
else -> null
}
if ( next != null && next != - 1 ) {
val nextView = act . findViewById < View ? > ( next )
if ( nextView != null ) {
nextView . requestFocus ( )
2022-01-18 00:24:23 +00:00
keyEventListener ?. invoke ( Pair ( event , true ) )
2022-01-07 19:27:25 +00:00
return true
}
}
when ( keyCode ) {
KeyEvent . KEYCODE _DPAD _CENTER -> {
if ( act . currentFocus is SearchView || act . currentFocus is SearchView . SearchAutoComplete ) {
UIHelper . showInputMethod ( act . currentFocus ?. findFocus ( ) )
}
}
}
}
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
}
}
}
2022-01-18 00:24:23 +00:00
if ( keyEventListener ?. invoke ( Pair ( event , false ) ) == true ) {
2022-01-07 19:27:25 +00:00
return true
}
return null
}
}