2021-04-30 17:20:15 +00:00
|
|
|
package com.lagradost.cloudstream3
|
|
|
|
|
2023-07-24 02:02:05 +00:00
|
|
|
import android.animation.ValueAnimator
|
2021-07-17 21:36:50 +00:00
|
|
|
import android.content.ComponentName
|
2023-01-21 22:22:48 +00:00
|
|
|
import android.content.Context
|
2021-07-04 00:59:51 +00:00
|
|
|
import android.content.Intent
|
2021-07-19 13:19:47 +00:00
|
|
|
import android.content.res.ColorStateList
|
2021-09-03 09:13:34 +00:00
|
|
|
import android.content.res.Configuration
|
2023-03-17 21:15:25 +00:00
|
|
|
import android.net.Uri
|
2023-03-10 20:33:13 +00:00
|
|
|
import android.os.Build
|
2021-04-30 17:20:15 +00:00
|
|
|
import android.os.Bundle
|
2023-01-21 22:22:48 +00:00
|
|
|
import android.util.AttributeSet
|
2022-04-03 15:00:50 +00:00
|
|
|
import android.util.Log
|
2023-07-31 13:35:42 +00:00
|
|
|
import android.view.KeyEvent
|
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuItem
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import android.view.WindowManager
|
2023-01-03 19:56:03 +00:00
|
|
|
import android.widget.Toast
|
2022-10-08 20:29:17 +00:00
|
|
|
import androidx.activity.result.ActivityResultLauncher
|
2022-03-03 11:26:26 +00:00
|
|
|
import androidx.annotation.IdRes
|
2023-07-27 19:47:42 +00:00
|
|
|
import androidx.annotation.MainThread
|
2022-09-01 12:13:54 +00:00
|
|
|
import androidx.appcompat.app.AlertDialog
|
2021-04-30 17:20:15 +00:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2022-12-28 11:51:55 +00:00
|
|
|
import androidx.constraintlayout.widget.ConstraintLayout
|
2023-07-31 13:35:42 +00:00
|
|
|
import androidx.core.view.children
|
2023-07-24 02:02:05 +00:00
|
|
|
import androidx.core.view.isGone
|
2021-09-20 21:11:36 +00:00
|
|
|
import androidx.core.view.isVisible
|
2022-09-17 11:03:41 +00:00
|
|
|
import androidx.fragment.app.FragmentActivity
|
2023-01-21 22:22:48 +00:00
|
|
|
import androidx.lifecycle.ViewModelProvider
|
2022-12-05 11:39:34 +00:00
|
|
|
import androidx.navigation.NavController
|
|
|
|
import androidx.navigation.NavDestination
|
2022-03-03 11:26:26 +00:00
|
|
|
import androidx.navigation.NavDestination.Companion.hierarchy
|
|
|
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
2022-12-05 11:39:34 +00:00
|
|
|
import androidx.navigation.NavOptions
|
2022-04-22 18:53:58 +00:00
|
|
|
import androidx.navigation.fragment.NavHostFragment
|
2021-09-20 21:11:36 +00:00
|
|
|
import androidx.navigation.ui.setupWithNavController
|
2021-08-30 21:42:58 +00:00
|
|
|
import androidx.preference.PreferenceManager
|
2023-08-01 02:03:43 +00:00
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2022-07-30 15:43:37 +00:00
|
|
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper
|
|
|
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
2023-07-31 13:35:42 +00:00
|
|
|
import com.google.android.gms.cast.framework.CastContext
|
|
|
|
import com.google.android.gms.cast.framework.Session
|
|
|
|
import com.google.android.gms.cast.framework.SessionManager
|
|
|
|
import com.google.android.gms.cast.framework.SessionManagerListener
|
2023-07-14 19:43:46 +00:00
|
|
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
2021-11-25 22:36:35 +00:00
|
|
|
import com.google.android.material.navigationrail.NavigationRailView
|
2023-02-21 17:43:35 +00:00
|
|
|
import com.google.android.material.snackbar.Snackbar
|
2021-08-06 20:55:11 +00:00
|
|
|
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
|
2022-03-16 15:29:11 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.allProviders
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.apis
|
2021-09-12 14:10:22 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
2022-06-02 22:51:41 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.initAll
|
2022-06-17 20:43:42 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
2022-10-08 20:29:17 +00:00
|
|
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
|
|
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.CommonActivity.loadThemes
|
|
|
|
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
|
|
|
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
|
|
|
import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint
|
2022-04-03 15:00:50 +00:00
|
|
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.CommonActivity.updateLocale
|
2023-07-14 19:43:46 +00:00
|
|
|
import com.lagradost.cloudstream3.databinding.ActivityMainBinding
|
|
|
|
import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding
|
|
|
|
import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding
|
2023-08-01 02:03:43 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.Resource
|
2023-07-31 13:35:42 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
|
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
|
|
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|
|
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
2022-05-02 16:58:27 +00:00
|
|
|
import com.lagradost.cloudstream3.network.initClient
|
2022-08-20 17:39:04 +00:00
|
|
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
2022-11-29 19:46:31 +00:00
|
|
|
import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
|
2022-08-21 01:44:49 +00:00
|
|
|
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
|
2021-07-08 19:39:49 +00:00
|
|
|
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
2022-06-07 16:38:24 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
2023-03-17 21:15:25 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
|
2022-08-20 17:39:04 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
|
2022-12-09 17:20:10 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
2022-11-13 00:40:49 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
|
2022-06-07 16:38:24 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
|
2021-09-12 14:10:22 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.APIRepository
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.WatchType
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
2022-12-09 17:20:10 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.home.HomeViewModel
|
2023-03-17 21:15:25 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.player.BasicLink
|
|
|
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
|
|
|
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
2022-12-09 17:20:10 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.setImage
|
|
|
|
import com.lagradost.cloudstream3.ui.result.setText
|
2023-07-14 19:43:46 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.txt
|
2022-11-13 00:40:49 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
2022-05-05 16:16:41 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
2022-02-18 19:54:55 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
2022-12-09 17:20:10 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
2021-10-30 18:14:12 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
2022-12-28 11:51:55 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
2022-08-07 12:14:51 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
2022-07-30 01:24:07 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
2022-08-20 17:39:04 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
2023-07-31 13:35:42 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.ApkInstaller
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
2021-09-24 22:51:08 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
2023-07-27 19:47:42 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
|
2023-02-21 17:43:35 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
2022-08-20 17:39:04 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
2021-07-30 01:14:53 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
2022-12-09 17:20:10 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
2023-01-20 00:16:05 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
2023-07-15 01:25:32 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
2022-03-04 15:39:56 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
2022-04-03 15:00:50 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
2023-02-21 17:43:35 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
2021-07-17 15:56:26 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
2022-03-16 15:29:11 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
2022-05-02 21:32:28 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
2023-07-31 13:35:42 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Event
|
|
|
|
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
2021-08-15 17:38:41 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
2022-02-18 19:54:55 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
2021-08-24 22:19:15 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
2022-02-05 01:05:13 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
2023-01-21 22:22:48 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
2021-08-24 22:19:15 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
2021-11-12 16:55:54 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
2021-09-23 10:50:10 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
2021-08-24 22:19:15 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
2023-07-27 19:47:42 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
2023-07-31 13:35:42 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
|
|
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
2022-05-02 16:58:27 +00:00
|
|
|
import com.lagradost.nicehttp.Requests
|
2022-07-30 15:43:37 +00:00
|
|
|
import com.lagradost.nicehttp.ResponseParser
|
2022-08-23 19:28:42 +00:00
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
2022-01-07 19:27:25 +00:00
|
|
|
import java.io.File
|
2023-07-24 02:02:05 +00:00
|
|
|
import java.lang.ref.WeakReference
|
2022-08-16 22:17:50 +00:00
|
|
|
import java.net.URI
|
2022-11-13 00:40:49 +00:00
|
|
|
import java.net.URLDecoder
|
2022-09-01 12:13:54 +00:00
|
|
|
import java.nio.charset.Charset
|
2023-07-27 19:47:42 +00:00
|
|
|
import kotlin.math.absoluteValue
|
2022-08-20 17:39:04 +00:00
|
|
|
import kotlin.reflect.KClass
|
2023-01-09 01:15:06 +00:00
|
|
|
import kotlin.system.exitProcess
|
2021-04-30 17:20:15 +00:00
|
|
|
|
2021-08-24 22:19:15 +00:00
|
|
|
|
2022-10-08 20:29:17 +00:00
|
|
|
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
|
|
|
|
//https://wiki.videolan.org/Android_Player_Intents/
|
|
|
|
|
|
|
|
//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904
|
|
|
|
//https://mpv-android.github.io/mpv-android/intent.html
|
2021-07-17 21:36:50 +00:00
|
|
|
|
2022-10-08 20:29:17 +00:00
|
|
|
// https://www.webvideocaster.com/integrations
|
2022-01-07 19:27:25 +00:00
|
|
|
|
2022-10-08 20:29:17 +00:00
|
|
|
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
|
|
|
|
|
|
|
|
const val VLC_PACKAGE = "org.videolan.vlc"
|
|
|
|
const val MPV_PACKAGE = "is.xyz.mpv"
|
2022-10-08 15:48:46 +00:00
|
|
|
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
|
|
|
|
|
2022-10-08 20:29:17 +00:00
|
|
|
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
|
|
|
|
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
|
|
|
|
|
|
|
|
//TODO REFACTOR AF
|
2022-11-30 20:23:19 +00:00
|
|
|
open class ResultResume(
|
2022-10-08 20:29:17 +00:00
|
|
|
val packageString: String,
|
|
|
|
val action: String = Intent.ACTION_VIEW,
|
|
|
|
val position: String? = null,
|
|
|
|
val duration: String? = null,
|
|
|
|
var launcher: ActivityResultLauncher<Intent>? = null,
|
|
|
|
) {
|
2022-11-30 20:23:19 +00:00
|
|
|
val defaultTime = -1L
|
|
|
|
|
2022-10-08 20:29:17 +00:00
|
|
|
val lastId get() = "${packageString}_last_open_id"
|
|
|
|
suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
|
|
|
|
val intent = Intent(action)
|
|
|
|
|
|
|
|
if (id != null)
|
|
|
|
setKey(lastId, id)
|
|
|
|
else
|
|
|
|
removeKey(lastId)
|
|
|
|
|
|
|
|
intent.setPackage(packageString)
|
|
|
|
callback.invoke(intent)
|
|
|
|
launcher?.launch(intent)
|
|
|
|
}
|
2022-11-30 20:23:19 +00:00
|
|
|
|
|
|
|
open fun getPosition(intent: Intent?): Long {
|
|
|
|
return defaultTime
|
|
|
|
}
|
|
|
|
|
|
|
|
open fun getDuration(intent: Intent?): Long {
|
|
|
|
return defaultTime
|
|
|
|
}
|
2022-10-08 20:29:17 +00:00
|
|
|
}
|
|
|
|
|
2022-11-30 20:23:19 +00:00
|
|
|
val VLC = object : ResultResume(
|
2022-10-08 20:29:17 +00:00
|
|
|
VLC_PACKAGE,
|
2023-03-10 20:33:13 +00:00
|
|
|
// Android 13 intent restrictions fucks up specifically launching the VLC player
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
"org.videolan.vlc.player.result"
|
|
|
|
} else {
|
|
|
|
Intent.ACTION_VIEW
|
|
|
|
},
|
2022-10-08 20:29:17 +00:00
|
|
|
"extra_position",
|
|
|
|
"extra_duration",
|
2022-11-30 20:23:19 +00:00
|
|
|
) {
|
|
|
|
override fun getPosition(intent: Intent?): Long {
|
|
|
|
return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
|
|
|
|
}
|
2022-10-08 20:29:17 +00:00
|
|
|
|
2022-11-30 20:23:19 +00:00
|
|
|
override fun getDuration(intent: Intent?): Long {
|
|
|
|
return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val MPV = object : ResultResume(
|
2022-10-08 20:29:17 +00:00
|
|
|
MPV_PACKAGE,
|
|
|
|
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
|
2022-11-13 00:40:49 +00:00
|
|
|
position = "position",
|
2022-10-08 20:29:17 +00:00
|
|
|
duration = "duration",
|
2022-11-30 20:23:19 +00:00
|
|
|
) {
|
|
|
|
override fun getPosition(intent: Intent?): Long {
|
|
|
|
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun getDuration(intent: Intent?): Long {
|
|
|
|
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
|
|
|
|
}
|
|
|
|
}
|
2022-10-08 20:29:17 +00:00
|
|
|
|
|
|
|
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
|
|
|
|
|
|
|
|
val resumeApps = arrayOf(
|
|
|
|
VLC, MPV, WEB_VIDEO
|
|
|
|
)
|
2022-10-08 15:48:46 +00:00
|
|
|
|
2021-12-05 16:22:30 +00:00
|
|
|
// Short name for requests client to make it nicer to use
|
2022-07-30 15:43:37 +00:00
|
|
|
|
|
|
|
var app = Requests(responseParser = object : ResponseParser {
|
|
|
|
val mapper: ObjectMapper = jacksonObjectMapper().configure(
|
|
|
|
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
|
|
|
false
|
|
|
|
)
|
|
|
|
|
|
|
|
override fun <T : Any> parse(text: String, kClass: KClass<T>): T {
|
|
|
|
return mapper.readValue(text, kClass.java)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): T? {
|
|
|
|
return try {
|
|
|
|
mapper.readValue(text, kClass.java)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun writeValueAsString(obj: Any): String {
|
|
|
|
return mapper.writeValueAsString(obj)
|
|
|
|
}
|
|
|
|
}).apply {
|
2022-05-07 11:18:22 +00:00
|
|
|
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
|
|
|
}
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-08-06 20:55:11 +00:00
|
|
|
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
2022-04-03 15:00:50 +00:00
|
|
|
companion object {
|
|
|
|
const val TAG = "MAINACT"
|
2022-08-23 19:28:42 +00:00
|
|
|
|
2022-11-13 00:40:49 +00:00
|
|
|
/**
|
|
|
|
* Setting this will automatically enter the query in the search
|
|
|
|
* next time the search fragment is opened.
|
|
|
|
* This variable will clear itself after one use. Null does nothing.
|
|
|
|
*
|
|
|
|
* This is a very bad solution but I was unable to find a better one.
|
|
|
|
**/
|
|
|
|
private var nextSearchQuery: String? = null
|
|
|
|
|
2022-08-23 19:28:42 +00:00
|
|
|
/**
|
|
|
|
* Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread
|
2022-12-13 22:28:31 +00:00
|
|
|
* Boolean signifies if stuff should be force reloaded (true if force reload, false if reload when necessary).
|
|
|
|
*
|
|
|
|
* The force reloading are used for plugin development to instantly reload the page on deployWithAdb
|
2022-08-23 19:28:42 +00:00
|
|
|
* */
|
2022-08-14 21:34:47 +00:00
|
|
|
val afterPluginsLoadedEvent = Event<Boolean>()
|
2022-08-21 01:44:49 +00:00
|
|
|
val mainPluginsLoadedEvent =
|
|
|
|
Event<Boolean>() // homepage api, used to speed up time to load for homepage
|
2022-08-15 01:31:33 +00:00
|
|
|
val afterRepositoryLoadedEvent = Event<Boolean>()
|
2022-09-17 11:03:41 +00:00
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
// kinda shitty solution, but cant com main->home otherwise for popups
|
|
|
|
val bookmarksUpdatedEvent = Event<Boolean>()
|
|
|
|
|
|
|
|
|
2022-09-17 11:03:41 +00:00
|
|
|
/**
|
|
|
|
* @return true if the str has launched an app task (be it successful or not)
|
|
|
|
* @param isWebview does not handle providers and opening download page if true. Can still add repos and login.
|
|
|
|
* */
|
2022-09-22 15:20:26 +00:00
|
|
|
fun handleAppIntentUrl(
|
|
|
|
activity: FragmentActivity?,
|
|
|
|
str: String?,
|
|
|
|
isWebview: Boolean
|
|
|
|
): Boolean =
|
2022-09-17 11:03:41 +00:00
|
|
|
with(activity) {
|
2023-03-17 21:15:25 +00:00
|
|
|
// TODO MUCH BETTER HANDLING
|
|
|
|
|
2022-11-13 00:40:49 +00:00
|
|
|
// Invalid URIs can crash
|
|
|
|
fun safeURI(uri: String) = normalSafeApiCall { URI(uri) }
|
|
|
|
|
2022-09-17 11:03:41 +00:00
|
|
|
if (str != null && this != null) {
|
|
|
|
if (str.startsWith("https://cs.repo")) {
|
|
|
|
val realUrl = "https://" + str.substringAfter("?")
|
|
|
|
println("Repository url: $realUrl")
|
|
|
|
loadRepository(realUrl)
|
|
|
|
return true
|
|
|
|
} else if (str.contains(appString)) {
|
|
|
|
for (api in OAuth2Apis) {
|
|
|
|
if (str.contains("/${api.redirectUrl}")) {
|
|
|
|
ioSafe {
|
|
|
|
Log.i(TAG, "handleAppIntent $str")
|
|
|
|
val isSuccessful = api.handleRedirect(str)
|
|
|
|
|
|
|
|
if (isSuccessful) {
|
|
|
|
Log.i(TAG, "authenticated ${api.name}")
|
|
|
|
} else {
|
|
|
|
Log.i(TAG, "failed to authenticate ${api.name}")
|
|
|
|
}
|
|
|
|
|
|
|
|
this@with.runOnUiThread {
|
|
|
|
try {
|
|
|
|
showToast(
|
|
|
|
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
|
|
|
|
api.name
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e) // format might fail
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2022-12-13 22:28:31 +00:00
|
|
|
// This specific intent is used for the gradle deployWithAdb
|
|
|
|
// https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46
|
|
|
|
if (str == "$appString:") {
|
|
|
|
PluginManager.hotReloadAllLocalPlugins(activity)
|
|
|
|
}
|
2022-11-13 00:40:49 +00:00
|
|
|
} else if (safeURI(str)?.scheme == appStringRepo) {
|
2022-09-17 11:03:41 +00:00
|
|
|
val url = str.replaceFirst(appStringRepo, "https")
|
|
|
|
loadRepository(url)
|
|
|
|
return true
|
2022-11-13 00:40:49 +00:00
|
|
|
} else if (safeURI(str)?.scheme == appStringSearch) {
|
|
|
|
nextSearchQuery =
|
|
|
|
URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8")
|
2023-02-28 00:19:59 +00:00
|
|
|
|
|
|
|
// Use both navigation views to support both layouts.
|
|
|
|
// It might be better to use the QuickSearch.
|
2023-07-14 19:43:46 +00:00
|
|
|
activity?.findViewById<BottomNavigationView>(R.id.nav_view)?.selectedItemId =
|
|
|
|
R.id.navigation_search
|
|
|
|
activity?.findViewById<NavigationRailView>(R.id.nav_rail_view)?.selectedItemId =
|
|
|
|
R.id.navigation_search
|
2023-03-17 21:15:25 +00:00
|
|
|
} else if (safeURI(str)?.scheme == appStringPlayer) {
|
|
|
|
val uri = Uri.parse(str)
|
|
|
|
val name = uri.getQueryParameter("name")
|
|
|
|
val url = URLDecoder.decode(uri.authority, "UTF-8")
|
|
|
|
|
|
|
|
navigate(
|
|
|
|
R.id.global_to_navigation_player,
|
|
|
|
GeneratorPlayer.newInstance(
|
|
|
|
LinkGenerator(
|
|
|
|
listOf(BasicLink(url, name)),
|
|
|
|
extract = true,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2022-12-09 17:20:10 +00:00
|
|
|
} else if (safeURI(str)?.scheme == appStringResumeWatching) {
|
|
|
|
val id =
|
|
|
|
str.substringAfter("$appStringResumeWatching://").toIntOrNull()
|
|
|
|
?: return false
|
|
|
|
ioSafe {
|
|
|
|
val resumeWatchingCard =
|
|
|
|
HomeViewModel.getResumeWatching()?.firstOrNull { it.id == id }
|
|
|
|
?: return@ioSafe
|
|
|
|
activity.loadSearchResult(
|
|
|
|
resumeWatchingCard,
|
|
|
|
START_ACTION_RESUME_LATEST
|
|
|
|
)
|
|
|
|
}
|
2022-09-22 15:20:26 +00:00
|
|
|
} else if (!isWebview) {
|
2022-09-17 11:03:41 +00:00
|
|
|
if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) {
|
|
|
|
this.navigate(R.id.navigation_downloads)
|
|
|
|
return true
|
|
|
|
} else {
|
2023-07-30 03:05:13 +00:00
|
|
|
synchronized(apis) {
|
|
|
|
for (api in apis) {
|
|
|
|
if (str.startsWith(api.mainUrl)) {
|
|
|
|
loadResult(str, api.name)
|
|
|
|
return true
|
|
|
|
}
|
2022-09-17 11:03:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2022-04-03 15:00:50 +00:00
|
|
|
}
|
|
|
|
|
2023-01-29 15:15:28 +00:00
|
|
|
var lastPopup: SearchResponse? = null
|
2023-01-21 22:22:48 +00:00
|
|
|
fun loadPopup(result: SearchResponse) {
|
|
|
|
lastPopup = result
|
|
|
|
viewModel.load(
|
|
|
|
this, result.url, result.apiName, false, if (getApiDubstatusSettings()
|
|
|
|
.contains(DubStatus.Dubbed)
|
|
|
|
) DubStatus.Dubbed else DubStatus.Subbed, null
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-08-06 20:55:11 +00:00
|
|
|
override fun onColorSelected(dialogId: Int, color: Int) {
|
|
|
|
onColorSelectedEvent.invoke(Pair(dialogId, color))
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDialogDismissed(dialogId: Int) {
|
|
|
|
onDialogDismissedEvent.invoke(dialogId)
|
|
|
|
}
|
2021-06-10 23:00:22 +00:00
|
|
|
|
2021-09-03 09:13:34 +00:00
|
|
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
|
|
super.onConfigurationChanged(newConfig)
|
|
|
|
updateLocale() // android fucks me by chaining lang when rotating the phone
|
2022-04-22 18:53:58 +00:00
|
|
|
|
2022-05-05 16:16:41 +00:00
|
|
|
val navHostFragment =
|
|
|
|
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
2022-04-22 18:53:58 +00:00
|
|
|
navHostFragment.navController.currentDestination?.let { updateNavBar(it) }
|
2021-12-12 02:33:17 +00:00
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
private fun updateNavBar(destination: NavDestination) {
|
2021-12-12 02:33:17 +00:00
|
|
|
this.hideKeyboard()
|
|
|
|
|
|
|
|
// Fucks up anime info layout since that has its own layout
|
2023-07-14 19:43:46 +00:00
|
|
|
binding?.castMiniControllerHolder?.isVisible =
|
2022-08-04 22:26:33 +00:00
|
|
|
!listOf(
|
|
|
|
R.id.navigation_results_phone,
|
|
|
|
R.id.navigation_results_tv,
|
|
|
|
R.id.navigation_player
|
|
|
|
).contains(destination.id)
|
2021-12-12 02:33:17 +00:00
|
|
|
|
|
|
|
val isNavVisible = listOf(
|
|
|
|
R.id.navigation_home,
|
|
|
|
R.id.navigation_search,
|
2023-01-28 22:38:02 +00:00
|
|
|
R.id.navigation_library,
|
2021-12-12 02:33:17 +00:00
|
|
|
R.id.navigation_downloads,
|
|
|
|
R.id.navigation_settings,
|
2022-05-15 18:38:32 +00:00
|
|
|
R.id.navigation_download_child,
|
|
|
|
R.id.navigation_subtitles,
|
|
|
|
R.id.navigation_chrome_subtitles,
|
|
|
|
R.id.navigation_settings_player,
|
|
|
|
R.id.navigation_settings_updates,
|
|
|
|
R.id.navigation_settings_ui,
|
|
|
|
R.id.navigation_settings_account,
|
2022-08-29 21:20:03 +00:00
|
|
|
R.id.navigation_settings_providers,
|
2022-06-09 13:50:55 +00:00
|
|
|
R.id.navigation_settings_general,
|
2022-08-07 21:11:13 +00:00
|
|
|
R.id.navigation_settings_extensions,
|
|
|
|
R.id.navigation_settings_plugins,
|
2023-02-15 20:40:10 +00:00
|
|
|
R.id.navigation_test_providers,
|
2021-12-12 02:33:17 +00:00
|
|
|
).contains(destination.id)
|
|
|
|
|
2022-12-28 11:51:55 +00:00
|
|
|
|
|
|
|
val dontPush = listOf(
|
|
|
|
R.id.navigation_home,
|
|
|
|
R.id.navigation_search,
|
|
|
|
R.id.navigation_results_phone,
|
|
|
|
R.id.navigation_results_tv,
|
|
|
|
R.id.navigation_player,
|
|
|
|
).contains(destination.id)
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
binding?.navHostFragment?.apply {
|
2022-12-28 11:51:55 +00:00
|
|
|
val params = layoutParams as ConstraintLayout.LayoutParams
|
2023-07-30 03:05:13 +00:00
|
|
|
val push =
|
|
|
|
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
|
2023-07-27 19:47:42 +00:00
|
|
|
|
2023-07-30 03:05:13 +00:00
|
|
|
if (!this.isLtr()) {
|
2023-07-27 19:47:42 +00:00
|
|
|
params.setMargins(
|
|
|
|
params.leftMargin,
|
|
|
|
params.topMargin,
|
|
|
|
push,
|
|
|
|
params.bottomMargin
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
params.setMargins(
|
|
|
|
push,
|
|
|
|
params.topMargin,
|
|
|
|
params.rightMargin,
|
|
|
|
params.bottomMargin
|
|
|
|
)
|
|
|
|
}
|
2022-12-28 11:51:55 +00:00
|
|
|
|
|
|
|
layoutParams = params
|
|
|
|
}
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
val landscape = when (resources.configuration.orientation) {
|
2021-12-12 02:33:17 +00:00
|
|
|
Configuration.ORIENTATION_LANDSCAPE -> {
|
|
|
|
true
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2021-12-12 02:33:17 +00:00
|
|
|
Configuration.ORIENTATION_PORTRAIT -> {
|
2023-07-14 19:43:46 +00:00
|
|
|
isTvSettings()
|
2021-12-12 02:33:17 +00:00
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2021-12-12 02:33:17 +00:00
|
|
|
else -> {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
binding?.apply {
|
|
|
|
navView.isVisible = isNavVisible && !landscape
|
|
|
|
navRailView.isVisible = isNavVisible && landscape
|
|
|
|
|
|
|
|
// Hide library on TV since it is not supported yet :(
|
|
|
|
val isTrueTv = isTrueTvSettings()
|
|
|
|
navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
|
|
|
navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
|
|
|
}
|
2021-09-03 09:13:34 +00:00
|
|
|
}
|
|
|
|
|
2021-11-25 22:36:35 +00:00
|
|
|
//private var mCastSession: CastSession? = null
|
2021-09-30 12:16:23 +00:00
|
|
|
lateinit var mSessionManager: SessionManager
|
|
|
|
private val mSessionManagerListener: SessionManagerListener<Session> by lazy { SessionManagerListenerImpl() }
|
2021-09-23 18:08:57 +00:00
|
|
|
|
|
|
|
private inner class SessionManagerListenerImpl : SessionManagerListener<Session> {
|
|
|
|
override fun onSessionStarting(session: Session) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionStarted(session: Session, sessionId: String) {
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionStartFailed(session: Session, i: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionEnding(session: Session) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionResumed(session: Session, wasSuspended: Boolean) {
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionResumeFailed(session: Session, i: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionSuspended(session: Session, i: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionEnded(session: Session, error: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSessionResuming(session: Session, s: String) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
super.onResume()
|
2022-08-23 19:28:42 +00:00
|
|
|
afterPluginsLoadedEvent += ::onAllPluginsLoaded
|
2021-09-24 22:51:08 +00:00
|
|
|
try {
|
2021-09-30 12:16:23 +00:00
|
|
|
if (isCastApiAvailable()) {
|
2021-11-25 22:36:35 +00:00
|
|
|
//mCastSession = mSessionManager.currentCastSession
|
2021-09-24 22:51:08 +00:00
|
|
|
mSessionManager.addSessionManagerListener(mSessionManagerListener)
|
|
|
|
}
|
2021-09-30 12:16:23 +00:00
|
|
|
} catch (e: Exception) {
|
2021-09-24 22:51:08 +00:00
|
|
|
logError(e)
|
|
|
|
}
|
2021-09-23 18:08:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
super.onPause()
|
2023-01-03 19:56:03 +00:00
|
|
|
|
|
|
|
// Start any delayed updates
|
|
|
|
if (ApkInstaller.delayedInstaller?.startInstallation() == true) {
|
|
|
|
Toast.makeText(this, R.string.update_started, Toast.LENGTH_LONG).show()
|
|
|
|
}
|
2021-09-24 22:51:08 +00:00
|
|
|
try {
|
2021-09-30 12:16:23 +00:00
|
|
|
if (isCastApiAvailable()) {
|
2021-09-24 22:51:08 +00:00
|
|
|
mSessionManager.removeSessionManagerListener(mSessionManagerListener)
|
2021-11-25 22:36:35 +00:00
|
|
|
//mCastSession = null
|
2021-09-24 22:51:08 +00:00
|
|
|
}
|
2021-09-30 12:16:23 +00:00
|
|
|
} catch (e: Exception) {
|
2021-09-24 22:51:08 +00:00
|
|
|
logError(e)
|
|
|
|
}
|
2021-09-23 18:08:57 +00:00
|
|
|
}
|
|
|
|
|
2021-11-26 23:19:17 +00:00
|
|
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
2023-07-27 19:47:42 +00:00
|
|
|
val start = System.currentTimeMillis()
|
|
|
|
try {
|
|
|
|
val response = CommonActivity.dispatchKeyEvent(this, event)
|
|
|
|
|
|
|
|
if (response != null)
|
|
|
|
return response
|
|
|
|
} finally {
|
|
|
|
debugAssert({
|
|
|
|
val end = System.currentTimeMillis()
|
|
|
|
val delta = end - start
|
|
|
|
delta > 100
|
|
|
|
}) {
|
|
|
|
"Took over 100ms to navigate, smth is VERY wrong"
|
|
|
|
}
|
2021-11-27 18:49:51 +00:00
|
|
|
}
|
2023-07-27 19:47:42 +00:00
|
|
|
|
2021-11-26 23:19:17 +00:00
|
|
|
return super.dispatchKeyEvent(event)
|
|
|
|
}
|
|
|
|
|
2021-10-27 14:54:57 +00:00
|
|
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
2022-01-07 19:27:25 +00:00
|
|
|
CommonActivity.onKeyDown(this, keyCode, event)
|
2021-10-30 18:14:12 +00:00
|
|
|
|
2021-10-27 14:54:57 +00:00
|
|
|
return super.onKeyDown(keyCode, event)
|
|
|
|
}
|
|
|
|
|
2021-05-22 22:25:56 +00:00
|
|
|
|
2021-06-10 23:00:22 +00:00
|
|
|
override fun onUserLeaveHint() {
|
|
|
|
super.onUserLeaveHint()
|
2022-01-07 19:27:25 +00:00
|
|
|
onUserLeaveHint(this)
|
2021-06-10 23:00:22 +00:00
|
|
|
}
|
2021-05-22 22:25:56 +00:00
|
|
|
|
2022-12-09 17:20:10 +00:00
|
|
|
private fun showConfirmExitDialog() {
|
2022-12-15 20:00:09 +00:00
|
|
|
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
|
2022-12-09 17:20:10 +00:00
|
|
|
builder.setTitle(R.string.confirm_exit_dialog)
|
|
|
|
builder.apply {
|
2023-01-09 01:15:06 +00:00
|
|
|
// Forceful exit since back button can actually go back to setup
|
|
|
|
setPositiveButton(R.string.yes) { _, _ -> exitProcess(0) }
|
2022-12-09 17:20:10 +00:00
|
|
|
setNegativeButton(R.string.no) { _, _ -> }
|
|
|
|
}
|
2023-01-20 00:16:05 +00:00
|
|
|
builder.show().setDefaultFocus()
|
2022-12-09 17:20:10 +00:00
|
|
|
}
|
|
|
|
|
2022-06-16 17:47:48 +00:00
|
|
|
private fun backPressed() {
|
2022-02-05 01:05:13 +00:00
|
|
|
this.window?.navigationBarColor =
|
|
|
|
this.colorFromAttribute(R.attr.primaryGrayBackground)
|
2021-09-02 16:51:13 +00:00
|
|
|
this.updateLocale()
|
2021-09-03 09:13:34 +00:00
|
|
|
this.updateLocale()
|
2022-12-09 17:20:10 +00:00
|
|
|
|
|
|
|
val navHostFragment =
|
|
|
|
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment
|
|
|
|
val navController = navHostFragment?.navController
|
|
|
|
val isAtHome =
|
|
|
|
navController?.currentDestination?.matchDestination(R.id.navigation_home) == true
|
|
|
|
|
|
|
|
if (isAtHome && isTrueTvSettings()) {
|
|
|
|
showConfirmExitDialog()
|
|
|
|
} else {
|
|
|
|
super.onBackPressed()
|
|
|
|
}
|
2021-05-18 13:43:32 +00:00
|
|
|
}
|
2021-04-30 17:20:15 +00:00
|
|
|
|
2022-06-16 17:47:48 +00:00
|
|
|
override fun onBackPressed() {
|
|
|
|
((supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.childFragmentManager?.primaryNavigationFragment as? IOnBackPressed)?.onBackPressed()
|
|
|
|
?.let { runNormal ->
|
|
|
|
if (runNormal) backPressed()
|
|
|
|
} ?: run {
|
|
|
|
backPressed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-08 17:46:47 +00:00
|
|
|
override fun onDestroy() {
|
|
|
|
val broadcastIntent = Intent()
|
|
|
|
broadcastIntent.action = "restart_service"
|
|
|
|
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
|
|
|
|
this.sendBroadcast(broadcastIntent)
|
2022-08-23 19:28:42 +00:00
|
|
|
afterPluginsLoadedEvent -= ::onAllPluginsLoaded
|
2021-07-08 17:46:47 +00:00
|
|
|
super.onDestroy()
|
|
|
|
}
|
|
|
|
|
2021-07-30 01:14:53 +00:00
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
|
|
handleAppIntent(intent)
|
|
|
|
super.onNewIntent(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleAppIntent(intent: Intent?) {
|
|
|
|
if (intent == null) return
|
|
|
|
val str = intent.dataString
|
2022-01-07 19:27:25 +00:00
|
|
|
loadCache()
|
2022-09-17 11:03:41 +00:00
|
|
|
handleAppIntentUrl(this, str, false)
|
2021-07-30 01:14:53 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 11:26:26 +00:00
|
|
|
private fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
|
|
|
|
hierarchy.any { it.id == destId }
|
|
|
|
|
|
|
|
private fun onNavDestinationSelected(item: MenuItem, navController: NavController): Boolean {
|
|
|
|
val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
|
|
|
|
.setEnterAnim(R.anim.enter_anim)
|
|
|
|
.setExitAnim(R.anim.exit_anim)
|
|
|
|
.setPopEnterAnim(R.anim.pop_enter)
|
|
|
|
.setPopExitAnim(R.anim.pop_exit)
|
|
|
|
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
|
|
|
|
builder.setPopUpTo(
|
|
|
|
navController.graph.findStartDestination().id,
|
|
|
|
inclusive = false,
|
|
|
|
saveState = true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
val options = builder.build()
|
|
|
|
return try {
|
|
|
|
navController.navigate(item.itemId, null, options)
|
|
|
|
navController.currentDestination?.matchDestination(item.itemId) == true
|
|
|
|
} catch (e: IllegalArgumentException) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-23 19:28:42 +00:00
|
|
|
private val pluginsLock = Mutex()
|
|
|
|
private fun onAllPluginsLoaded(success: Boolean = false) {
|
|
|
|
ioSafe {
|
|
|
|
pluginsLock.withLock {
|
2023-07-30 03:05:13 +00:00
|
|
|
synchronized(allProviders) {
|
|
|
|
// Load cloned sites after plugins have been loaded since clones depend on plugins.
|
|
|
|
try {
|
|
|
|
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
|
|
|
list.forEach { custom ->
|
|
|
|
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
|
|
|
|
?.let {
|
|
|
|
allProviders.add(it.javaClass.newInstance().apply {
|
|
|
|
name = custom.name
|
|
|
|
lang = custom.lang
|
|
|
|
mainUrl = custom.url.trimEnd('/')
|
|
|
|
canBeOverridden = false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-08-23 19:28:42 +00:00
|
|
|
}
|
2023-07-30 03:05:13 +00:00
|
|
|
// it.hashCode() is not enough to make sure they are distinct
|
|
|
|
apis =
|
|
|
|
allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
|
|
|
|
APIHolder.apiMap = null
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
2022-08-23 19:28:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
lateinit var viewModel: ResultViewModel2
|
|
|
|
|
|
|
|
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
|
|
|
viewModel =
|
|
|
|
ViewModelProvider(this)[ResultViewModel2::class.java]
|
|
|
|
|
|
|
|
return super.onCreateView(name, context, attrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun hidePreviewPopupDialog() {
|
|
|
|
bottomPreviewPopup.dismissSafe(this)
|
2023-07-14 19:43:46 +00:00
|
|
|
bottomPreviewPopup = null
|
|
|
|
bottomPreviewBinding = null
|
2023-01-21 22:22:48 +00:00
|
|
|
}
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
private var bottomPreviewPopup: BottomSheetDialog? = null
|
|
|
|
private var bottomPreviewBinding: BottomResultviewPreviewBinding? = null
|
|
|
|
private fun showPreviewPopupDialog(): BottomResultviewPreviewBinding {
|
|
|
|
val ret = (bottomPreviewBinding ?: run {
|
2023-01-21 22:22:48 +00:00
|
|
|
val builder =
|
|
|
|
BottomSheetDialog(this)
|
2023-07-14 19:43:46 +00:00
|
|
|
val binding: BottomResultviewPreviewBinding =
|
|
|
|
BottomResultviewPreviewBinding.inflate(builder.layoutInflater, null, false)
|
|
|
|
bottomPreviewBinding = binding
|
|
|
|
builder.setContentView(binding.root)
|
2023-01-21 22:22:48 +00:00
|
|
|
builder.setOnDismissListener {
|
|
|
|
bottomPreviewPopup = null
|
2023-07-14 19:43:46 +00:00
|
|
|
bottomPreviewBinding = null
|
2023-01-21 22:22:48 +00:00
|
|
|
viewModel.clear()
|
|
|
|
}
|
|
|
|
builder.setCanceledOnTouchOutside(true)
|
|
|
|
builder.show()
|
2023-07-14 19:43:46 +00:00
|
|
|
bottomPreviewPopup = builder
|
|
|
|
binding
|
2023-01-21 22:22:48 +00:00
|
|
|
})
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
return ret
|
|
|
|
}
|
2022-08-23 19:28:42 +00:00
|
|
|
|
2023-08-01 02:03:43 +00:00
|
|
|
var binding: ActivityMainBinding? = null
|
2023-07-27 19:47:42 +00:00
|
|
|
|
|
|
|
object TvFocus {
|
|
|
|
data class FocusTarget(
|
|
|
|
val width: Int,
|
|
|
|
val height: Int,
|
|
|
|
val x: Float,
|
|
|
|
val y: Float,
|
|
|
|
) {
|
|
|
|
companion object {
|
|
|
|
fun lerp(a: FocusTarget, b: FocusTarget, lerp: Float): FocusTarget {
|
|
|
|
val ilerp = 1 - lerp
|
|
|
|
return FocusTarget(
|
|
|
|
width = (a.width * ilerp + b.width * lerp).toInt(),
|
|
|
|
height = (a.height * ilerp + b.height * lerp).toInt(),
|
|
|
|
x = a.x * ilerp + b.x * lerp,
|
|
|
|
y = a.y * ilerp + b.y * lerp
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-07-25 19:15:10 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
var last: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
|
|
|
|
var current: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
|
|
|
|
|
|
|
|
var focusOutline: WeakReference<View> = WeakReference(null)
|
|
|
|
var lastFocus: WeakReference<View> = WeakReference(null)
|
|
|
|
private val layoutListener: View.OnLayoutChangeListener =
|
2023-08-01 02:03:43 +00:00
|
|
|
View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
|
|
// shitty fix for layouts
|
|
|
|
lastFocus.get()?.apply {
|
|
|
|
updateFocusView(
|
|
|
|
this, same = true
|
|
|
|
)
|
|
|
|
postDelayed({
|
|
|
|
updateFocusView(
|
|
|
|
lastFocus.get(), same = false
|
|
|
|
)
|
|
|
|
}, 300)
|
|
|
|
}
|
2023-07-27 19:47:42 +00:00
|
|
|
}
|
|
|
|
private val attachListener: View.OnAttachStateChangeListener =
|
|
|
|
object : View.OnAttachStateChangeListener {
|
|
|
|
override fun onViewAttachedToWindow(v: View) {
|
|
|
|
updateFocusView(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewDetachedFromWindow(v: View) {
|
|
|
|
// removes the focus view but not the listener as updateFocusView(null) will remove the listener
|
|
|
|
focusOutline.get()?.isVisible = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun setTargetPosition(target: FocusTarget) {
|
|
|
|
focusOutline.get()?.apply {
|
|
|
|
layoutParams = layoutParams?.apply {
|
|
|
|
width = target.width
|
|
|
|
height = target.height
|
|
|
|
}
|
|
|
|
|
|
|
|
translationX = target.x
|
|
|
|
translationY = target.y
|
2023-07-30 03:05:13 +00:00
|
|
|
bringToFront()
|
2023-07-27 19:47:42 +00:00
|
|
|
}
|
2023-07-25 19:15:10 +00:00
|
|
|
}
|
2023-07-24 02:02:05 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
private var animator: ValueAnimator? = null
|
|
|
|
|
|
|
|
@MainThread
|
|
|
|
fun updateFocusView(newFocus: View?, same: Boolean = false) {
|
|
|
|
val focusOutline = focusOutline.get() ?: return
|
2023-08-01 02:03:43 +00:00
|
|
|
val lastView = lastFocus.get()
|
|
|
|
val exactlyTheSame = lastView == newFocus && newFocus != null
|
|
|
|
if (!exactlyTheSame) {
|
|
|
|
lastView?.removeOnLayoutChangeListener(layoutListener)
|
|
|
|
lastView?.removeOnAttachStateChangeListener(attachListener)
|
|
|
|
(lastView?.parent as? RecyclerView)?.removeOnLayoutChangeListener(layoutListener)
|
2023-07-26 03:44:37 +00:00
|
|
|
}
|
2023-07-24 02:02:05 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
val wasGone = focusOutline.isGone
|
|
|
|
|
|
|
|
val visible =
|
|
|
|
newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isShown && newFocus.tag != "tv_no_focus_tag"
|
|
|
|
focusOutline.isVisible = visible
|
2023-07-24 02:02:05 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
if (newFocus != null) {
|
|
|
|
lastFocus = WeakReference(newFocus)
|
2023-07-24 02:02:05 +00:00
|
|
|
|
2023-08-01 02:03:43 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
val out = IntArray(2)
|
|
|
|
newFocus.getLocationInWindow(out)
|
|
|
|
val (screenX, screenY) = out
|
2023-07-30 03:05:13 +00:00
|
|
|
var (x, y) = screenX.toFloat() to screenY.toFloat()
|
2023-07-27 19:47:42 +00:00
|
|
|
val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY
|
2023-07-30 03:05:13 +00:00
|
|
|
// println(">><<< $x $y $currentX $currentY")
|
|
|
|
if (!newFocus.isLtr()) {
|
2023-07-27 19:47:42 +00:00
|
|
|
x = x - focusOutline.rootView.width + newFocus.measuredWidth
|
|
|
|
}
|
|
|
|
|
|
|
|
// out of bounds = 0,0
|
|
|
|
if (screenX == 0 && screenY == 0) {
|
|
|
|
focusOutline.isVisible = false
|
|
|
|
}
|
2023-08-01 02:03:43 +00:00
|
|
|
if (!exactlyTheSame) {
|
|
|
|
(newFocus.parent as? RecyclerView)?.addOnLayoutChangeListener(layoutListener)
|
|
|
|
newFocus.addOnLayoutChangeListener(layoutListener)
|
|
|
|
newFocus.addOnAttachStateChangeListener(attachListener)
|
|
|
|
}
|
2023-07-27 19:47:42 +00:00
|
|
|
val start = FocusTarget(
|
|
|
|
x = currentX,
|
|
|
|
y = currentY,
|
|
|
|
width = focusOutline.measuredWidth,
|
|
|
|
height = focusOutline.measuredHeight
|
|
|
|
)
|
|
|
|
val end = FocusTarget(
|
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
width = newFocus.measuredWidth,
|
|
|
|
height = newFocus.measuredHeight
|
|
|
|
)
|
|
|
|
|
|
|
|
// if they are the same within then snap, aka scrolling
|
|
|
|
val deltaMin = 50.toPx
|
|
|
|
if (start.width == end.width && start.height == end.height && (start.x - end.x).absoluteValue < deltaMin && (start.y - end.y).absoluteValue < deltaMin) {
|
|
|
|
animator?.cancel()
|
|
|
|
last = start
|
|
|
|
current = end
|
|
|
|
setTargetPosition(end)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// if running then "reuse"
|
|
|
|
if (animator?.isRunning == true) {
|
|
|
|
current = end
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
animator?.cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
last = start
|
|
|
|
current = end
|
|
|
|
|
|
|
|
// if previously gone, then tp
|
|
|
|
if (wasGone) {
|
|
|
|
setTargetPosition(current)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// animate between a and b
|
|
|
|
animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
|
|
|
|
startDelay = 0
|
|
|
|
duration = 100
|
|
|
|
addUpdateListener { animation ->
|
|
|
|
val animatedValue = animation.animatedValue as Float
|
|
|
|
val target = FocusTarget.lerp(last, current, minOf(animatedValue, 1.0f))
|
|
|
|
setTargetPosition(target)
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
2023-07-27 19:47:42 +00:00
|
|
|
start()
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
// post check
|
|
|
|
if (!same) {
|
|
|
|
newFocus.postDelayed({
|
|
|
|
updateFocusView(lastFocus.get(), same = true)
|
|
|
|
}, 200)
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
/*
|
|
|
|
|
|
|
|
the following is working, but somewhat bad code code
|
|
|
|
|
|
|
|
if (!wasGone) {
|
|
|
|
(focusOutline.parent as? ViewGroup)?.let {
|
|
|
|
TransitionManager.endTransitions(it)
|
|
|
|
TransitionManager.beginDelayedTransition(
|
|
|
|
it,
|
|
|
|
TransitionSet().addTransition(ChangeBounds())
|
|
|
|
.addTransition(ChangeTransform())
|
|
|
|
.setDuration(100)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-07-24 02:02:05 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
focusOutline.layoutParams = focusOutline.layoutParams?.apply {
|
|
|
|
width = newFocus.measuredWidth
|
|
|
|
height = newFocus.measuredHeight
|
|
|
|
}
|
|
|
|
focusOutline.translationX = x.toFloat()
|
|
|
|
focusOutline.translationY = y.toFloat()*/
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2023-07-27 19:47:42 +00:00
|
|
|
|
2021-04-30 17:20:15 +00:00
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
2022-08-06 18:27:56 +00:00
|
|
|
app.initClient(this)
|
2022-08-11 17:12:27 +00:00
|
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
2022-09-01 12:13:54 +00:00
|
|
|
|
|
|
|
val errorFile = filesDir.resolve("last_error")
|
|
|
|
var lastError: String? = null
|
|
|
|
if (errorFile.exists() && errorFile.isFile) {
|
|
|
|
lastError = errorFile.readText(Charset.defaultCharset())
|
|
|
|
errorFile.delete()
|
|
|
|
}
|
2022-09-17 11:03:41 +00:00
|
|
|
|
2022-08-28 18:32:17 +00:00
|
|
|
val settingsForProvider = SettingsJson()
|
2022-09-17 11:03:41 +00:00
|
|
|
settingsForProvider.enableAdult =
|
|
|
|
settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false)
|
2022-08-28 18:32:17 +00:00
|
|
|
|
|
|
|
MainAPI.settingsForProvider = settingsForProvider
|
2022-08-11 17:12:27 +00:00
|
|
|
|
2022-08-14 11:23:06 +00:00
|
|
|
loadThemes(this)
|
|
|
|
updateLocale()
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
try {
|
|
|
|
if (isCastApiAvailable()) {
|
|
|
|
mSessionManager = CastContext.getSharedInstance(this).sessionManager
|
|
|
|
}
|
2023-07-15 01:25:32 +00:00
|
|
|
} catch (t: Throwable) {
|
|
|
|
logError(t)
|
2022-08-14 11:23:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
2022-12-28 11:51:55 +00:00
|
|
|
updateTv()
|
2023-07-14 00:28:49 +00:00
|
|
|
|
2023-07-15 01:25:32 +00:00
|
|
|
// backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting?
|
|
|
|
try {
|
|
|
|
val appVer = BuildConfig.VERSION_NAME
|
2023-07-24 02:02:05 +00:00
|
|
|
val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: ""
|
2023-07-15 01:25:32 +00:00
|
|
|
if (appVer != lastAppAutoBackup) {
|
|
|
|
setKey("VERSION_NAME", BuildConfig.VERSION_NAME)
|
|
|
|
backup()
|
|
|
|
}
|
2023-07-24 02:02:05 +00:00
|
|
|
} catch (t: Throwable) {
|
2023-07-15 01:25:32 +00:00
|
|
|
logError(t)
|
|
|
|
}
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
|
|
|
|
binding = try {
|
|
|
|
if (isTvSettings()) {
|
|
|
|
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
|
|
|
|
setContentView(newLocalBinding.root)
|
2023-07-27 19:47:42 +00:00
|
|
|
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
|
2023-07-24 02:02:05 +00:00
|
|
|
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
|
|
|
// println("refocus $oldFocus -> $newFocus")
|
2023-07-27 19:47:42 +00:00
|
|
|
TvFocus.updateFocusView(newFocus)
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
|
|
|
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
|
2023-07-27 19:47:42 +00:00
|
|
|
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
|
2023-07-24 02:02:05 +00:00
|
|
|
}
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
|
|
|
|
} else {
|
|
|
|
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
|
|
|
|
setContentView(newLocalBinding.root)
|
|
|
|
newLocalBinding
|
|
|
|
}
|
|
|
|
} catch (t: Throwable) {
|
2023-07-17 01:32:41 +00:00
|
|
|
showToast(txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG)
|
2023-07-14 19:43:46 +00:00
|
|
|
null
|
2022-08-14 11:23:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
changeStatusBarState(isEmulatorSettings())
|
|
|
|
|
2023-02-21 17:43:35 +00:00
|
|
|
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
|
|
|
|
if (this.getKey<Boolean>(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
|
|
|
|
main {
|
|
|
|
if (checkGithubConnectivity()) {
|
|
|
|
this.setKey(getString(R.string.jsdelivr_proxy_key), false)
|
|
|
|
} else {
|
|
|
|
this.setKey(getString(R.string.jsdelivr_proxy_key), true)
|
|
|
|
val parentView: View = findViewById(android.R.id.content)
|
2023-03-10 20:33:13 +00:00
|
|
|
Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG)
|
|
|
|
.let { snackbar ->
|
|
|
|
snackbar.setAction(R.string.revert) {
|
|
|
|
setKey(getString(R.string.jsdelivr_proxy_key), false)
|
|
|
|
}
|
|
|
|
snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground))
|
|
|
|
snackbar.setTextColor(colorFromAttribute(R.attr.textColor))
|
|
|
|
snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary))
|
|
|
|
snackbar.show()
|
2023-02-21 17:43:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-29 15:15:28 +00:00
|
|
|
|
|
|
|
if (PluginManager.checkSafeModeFile()) {
|
|
|
|
normalSafeApiCall {
|
2023-07-24 02:02:05 +00:00
|
|
|
showToast(R.string.safe_mode_file, Toast.LENGTH_LONG)
|
2023-01-29 15:15:28 +00:00
|
|
|
}
|
|
|
|
} else if (lastError == null) {
|
2022-08-23 19:28:42 +00:00
|
|
|
ioSafe {
|
2022-09-01 12:13:54 +00:00
|
|
|
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
|
|
|
|
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
|
|
|
|
} ?: run {
|
|
|
|
mainPluginsLoadedEvent.invoke(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
ioSafe {
|
2022-09-17 11:03:41 +00:00
|
|
|
if (settingsManager.getBoolean(
|
|
|
|
getString(R.string.auto_update_plugins_key),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
) {
|
2022-09-01 12:13:54 +00:00
|
|
|
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
|
|
|
|
} else {
|
2022-11-29 19:46:31 +00:00
|
|
|
loadAllOnlinePlugins(this@MainActivity)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Automatically download not existing plugins
|
|
|
|
if (settingsManager.getBoolean(
|
|
|
|
getString(R.string.auto_download_plugins_key),
|
|
|
|
false
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
PluginManager.downloadNotExistingPluginsAndLoad(this@MainActivity)
|
2022-09-01 12:13:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ioSafe {
|
2022-12-13 22:28:31 +00:00
|
|
|
PluginManager.loadAllLocalPlugins(this@MainActivity, false)
|
2022-08-17 12:26:36 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-01 12:13:54 +00:00
|
|
|
} else {
|
|
|
|
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
|
|
|
|
builder.setTitle(R.string.safe_mode_title)
|
|
|
|
builder.setMessage(R.string.safe_mode_description)
|
|
|
|
builder.apply {
|
|
|
|
setPositiveButton(R.string.safe_mode_crash_info) { _, _ ->
|
|
|
|
val tbBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
|
|
|
|
tbBuilder.setTitle(R.string.safe_mode_title)
|
|
|
|
tbBuilder.setMessage(lastError)
|
|
|
|
tbBuilder.show()
|
|
|
|
}
|
2022-08-17 12:26:36 +00:00
|
|
|
|
2022-09-01 12:13:54 +00:00
|
|
|
setNegativeButton("Ok") { _, _ -> }
|
2022-08-23 19:28:42 +00:00
|
|
|
}
|
2023-01-20 00:16:05 +00:00
|
|
|
builder.show().setDefaultFocus()
|
2022-08-14 16:13:44 +00:00
|
|
|
}
|
2022-06-16 01:04:24 +00:00
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
observeNullable(viewModel.page) { resource ->
|
|
|
|
if (resource == null) {
|
2023-07-14 19:43:46 +00:00
|
|
|
hidePreviewPopupDialog()
|
2023-01-21 22:22:48 +00:00
|
|
|
return@observeNullable
|
|
|
|
}
|
|
|
|
when (resource) {
|
|
|
|
is Resource.Failure -> {
|
2023-07-17 01:32:41 +00:00
|
|
|
showToast(R.string.error)
|
2023-07-14 19:43:46 +00:00
|
|
|
viewModel.clear()
|
2023-01-21 22:22:48 +00:00
|
|
|
hidePreviewPopupDialog()
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
is Resource.Loading -> {
|
|
|
|
showPreviewPopupDialog().apply {
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewLoading.isVisible = true
|
|
|
|
resultviewPreviewResult.isVisible = false
|
|
|
|
resultviewPreviewLoadingShimmer.startShimmer()
|
2023-01-21 22:22:48 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
|
2023-01-21 22:22:48 +00:00
|
|
|
is Resource.Success -> {
|
|
|
|
val d = resource.value
|
|
|
|
showPreviewPopupDialog().apply {
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewLoading.isVisible = false
|
|
|
|
resultviewPreviewResult.isVisible = true
|
|
|
|
resultviewPreviewLoadingShimmer.stopShimmer()
|
2023-01-21 22:22:48 +00:00
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewTitle.text = d.title
|
2023-01-21 22:22:48 +00:00
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewMetaType.setText(d.typeText)
|
|
|
|
resultviewPreviewMetaYear.setText(d.yearText)
|
|
|
|
resultviewPreviewMetaDuration.setText(d.durationText)
|
|
|
|
resultviewPreviewMetaRating.setText(d.ratingText)
|
2023-01-21 22:22:48 +00:00
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewDescription.setText(d.plotText)
|
|
|
|
resultviewPreviewPoster.setImage(
|
2023-01-21 22:22:48 +00:00
|
|
|
d.posterImage ?: d.posterBackgroundImage
|
|
|
|
)
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewPoster.setOnClickListener {
|
2023-01-21 22:22:48 +00:00
|
|
|
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
|
|
|
|
val value = viewModel.watchStatus.value ?: WatchType.NONE
|
|
|
|
|
|
|
|
this@MainActivity.showBottomDialog(
|
|
|
|
WatchType.values().map { getString(it.stringRes) }.toList(),
|
|
|
|
value.ordinal,
|
|
|
|
this@MainActivity.getString(R.string.action_add_to_bookmarks),
|
|
|
|
showApply = false,
|
|
|
|
{}) {
|
|
|
|
viewModel.updateWatchStatus(WatchType.values()[it])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isTvSettings()) // dont want this clickable on tv layout
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewDescription.setOnClickListener { view ->
|
2023-01-21 22:22:48 +00:00
|
|
|
view.context?.let { ctx ->
|
|
|
|
val builder: AlertDialog.Builder =
|
|
|
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
|
|
|
builder.setMessage(d.plotText.asString(ctx).html())
|
|
|
|
.setTitle(d.plotHeaderText.asString(ctx))
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-14 19:43:46 +00:00
|
|
|
resultviewPreviewMoreInfo.setOnClickListener {
|
|
|
|
viewModel.clear()
|
2023-01-21 22:22:48 +00:00
|
|
|
hidePreviewPopupDialog()
|
|
|
|
lastPopup?.let {
|
|
|
|
loadSearchResult(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 12:13:54 +00:00
|
|
|
|
2022-08-06 18:27:56 +00:00
|
|
|
// ioSafe {
|
|
|
|
// val plugins =
|
|
|
|
// RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json")
|
|
|
|
// ?: emptyList()
|
|
|
|
// plugins.map {
|
|
|
|
// println("Load plugin: ${it.name} ${it.url}")
|
|
|
|
// RepositoryParser.loadSiteTemp(applicationContext, it.url, it.name)
|
|
|
|
// }
|
|
|
|
// }
|
2022-05-26 17:04:39 +00:00
|
|
|
|
2021-11-08 18:13:39 +00:00
|
|
|
// init accounts
|
2022-08-14 16:13:44 +00:00
|
|
|
ioSafe {
|
|
|
|
for (api in accountManagers) {
|
|
|
|
api.init()
|
|
|
|
}
|
2022-03-16 15:29:11 +00:00
|
|
|
|
2022-10-31 00:16:15 +00:00
|
|
|
inAppAuths.amap { api ->
|
2022-06-07 16:38:24 +00:00
|
|
|
try {
|
|
|
|
api.initialize()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 16:16:41 +00:00
|
|
|
SearchResultBuilder.updateCache(this)
|
|
|
|
|
2022-08-14 16:13:44 +00:00
|
|
|
ioSafe {
|
|
|
|
initAll()
|
2022-08-14 21:34:47 +00:00
|
|
|
// No duplicates (which can happen by registerMainAPI)
|
2023-07-30 03:05:13 +00:00
|
|
|
apis = synchronized(allProviders) {
|
|
|
|
allProviders.distinctBy { it }
|
|
|
|
}
|
2022-08-07 12:14:51 +00:00
|
|
|
}
|
|
|
|
|
2021-07-30 01:14:53 +00:00
|
|
|
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
2022-03-04 15:39:56 +00:00
|
|
|
setUpBackup()
|
2022-01-07 19:27:25 +00:00
|
|
|
|
|
|
|
CommonActivity.init(this)
|
2022-05-05 16:16:41 +00:00
|
|
|
val navHostFragment =
|
|
|
|
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
2022-04-22 18:53:58 +00:00
|
|
|
val navController = navHostFragment.navController
|
2022-11-13 00:40:49 +00:00
|
|
|
|
|
|
|
navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? ->
|
|
|
|
// Intercept search and add a query
|
2023-07-31 13:35:42 +00:00
|
|
|
updateNavBar(navDestination)
|
2022-11-13 00:40:49 +00:00
|
|
|
if (navDestination.matchDestination(R.id.navigation_search) && !nextSearchQuery.isNullOrBlank()) {
|
|
|
|
bundle?.apply {
|
|
|
|
this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery)
|
|
|
|
nextSearchQuery = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:53:58 +00:00
|
|
|
//val navController = findNavController(R.id.nav_host_fragment)
|
2021-07-19 13:19:47 +00:00
|
|
|
|
2021-09-20 21:11:36 +00:00
|
|
|
/*navOptions = NavOptions.Builder()
|
2021-07-19 13:19:47 +00:00
|
|
|
.setLaunchSingleTop(true)
|
|
|
|
.setEnterAnim(R.anim.nav_enter_anim)
|
|
|
|
.setExitAnim(R.anim.nav_exit_anim)
|
|
|
|
.setPopEnterAnim(R.anim.nav_pop_enter)
|
|
|
|
.setPopExitAnim(R.anim.nav_pop_exit)
|
|
|
|
.setPopUpTo(navController.graph.startDestination, false)
|
2021-09-20 21:11:36 +00:00
|
|
|
.build()*/
|
2022-03-03 11:26:26 +00:00
|
|
|
|
2023-07-31 13:35:42 +00:00
|
|
|
val rippleColor = ColorStateList.valueOf(getResourceColor(R.attr.colorPrimary, 0.1f))
|
|
|
|
|
|
|
|
binding?.navView?.apply {
|
|
|
|
itemRippleColor = rippleColor
|
|
|
|
itemActiveIndicatorColor = rippleColor
|
|
|
|
setupWithNavController(navController)
|
|
|
|
setOnItemSelectedListener { item ->
|
|
|
|
onNavDestinationSelected(
|
|
|
|
item,
|
|
|
|
navController
|
|
|
|
)
|
|
|
|
}
|
2022-12-28 11:51:55 +00:00
|
|
|
}
|
2023-07-31 13:35:42 +00:00
|
|
|
|
|
|
|
binding?.navRailView?.apply {
|
|
|
|
itemRippleColor = rippleColor
|
|
|
|
itemActiveIndicatorColor = rippleColor
|
|
|
|
setupWithNavController(navController)
|
|
|
|
if (isTvSettings()) {
|
|
|
|
background?.alpha = 200
|
|
|
|
} else {
|
|
|
|
background?.alpha = 255
|
|
|
|
}
|
|
|
|
|
|
|
|
setOnItemSelectedListener { item ->
|
|
|
|
onNavDestinationSelected(
|
|
|
|
item,
|
|
|
|
navController
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun noFocus(view: View) {
|
|
|
|
view.tag = view.context.getString(R.string.tv_no_focus_tag)
|
|
|
|
(view as? ViewGroup)?.let {
|
|
|
|
for (child in it.children) {
|
|
|
|
noFocus(child)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
noFocus(this)
|
2021-09-20 21:11:36 +00:00
|
|
|
}
|
2022-03-03 11:26:26 +00:00
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
loadCache()
|
2022-06-17 20:43:42 +00:00
|
|
|
updateHasTrailers()
|
2021-09-20 21:11:36 +00:00
|
|
|
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
2021-07-19 13:19:47 +00:00
|
|
|
when (item.itemId) {
|
2021-07-28 01:04:32 +00:00
|
|
|
R.id.navigation_home -> {
|
|
|
|
navController.navigate(R.id.navigation_home, null, navOptions)
|
|
|
|
}
|
2021-07-19 13:19:47 +00:00
|
|
|
R.id.navigation_search -> {
|
|
|
|
navController.navigate(R.id.navigation_search, null, navOptions)
|
|
|
|
}
|
|
|
|
R.id.navigation_downloads -> {
|
|
|
|
navController.navigate(R.id.navigation_downloads, null, navOptions)
|
|
|
|
}
|
|
|
|
R.id.navigation_settings -> {
|
|
|
|
navController.navigate(R.id.navigation_settings, null, navOptions)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
2021-09-20 21:11:36 +00:00
|
|
|
}*/
|
2021-07-19 13:19:47 +00:00
|
|
|
|
2021-05-20 21:25:41 +00:00
|
|
|
|
|
|
|
if (!checkWrite()) {
|
|
|
|
requestRW()
|
|
|
|
if (checkWrite()) return
|
|
|
|
}
|
2023-07-14 19:43:46 +00:00
|
|
|
//CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
|
2021-06-14 16:58:43 +00:00
|
|
|
|
2021-07-15 16:45:25 +00:00
|
|
|
// THIS IS CURRENTLY REMOVED BECAUSE HIGHER VERS OF ANDROID NEEDS A NOTIFICATION
|
|
|
|
//if (!VideoDownloadManager.isMyServiceRunning(this, VideoDownloadKeepAliveService::class.java)) {
|
|
|
|
// val mYourService = VideoDownloadKeepAliveService()
|
|
|
|
// val mServiceIntent = Intent(this, mYourService::class.java).putExtra(START_VALUE_KEY, RESTART_ALL_DOWNLOADS_AND_QUEUE)
|
|
|
|
// this.startService(mServiceIntent)
|
|
|
|
//}
|
2021-07-17 15:56:26 +00:00
|
|
|
//settingsManager.getBoolean("disable_automatic_data_downloads", true) &&
|
2021-07-25 14:25:09 +00:00
|
|
|
|
|
|
|
// TODO RETURN TO TRUE
|
|
|
|
/*
|
2021-07-17 21:36:50 +00:00
|
|
|
if (isUsingMobileData()) {
|
2021-07-17 15:56:26 +00:00
|
|
|
Toast.makeText(this, "Downloads not resumed on mobile data", Toast.LENGTH_LONG).show()
|
|
|
|
} else {
|
|
|
|
val keys = getKeys(VideoDownloadManager.KEY_RESUME_PACKAGES)
|
|
|
|
val resumePkg = keys.mapNotNull { k -> getKey<VideoDownloadManager.DownloadResumePackage>(k) }
|
|
|
|
|
|
|
|
// To remove a bug where this is permanent
|
|
|
|
removeKeys(VideoDownloadManager.KEY_RESUME_PACKAGES)
|
|
|
|
|
|
|
|
for (pkg in resumePkg) { // ADD ALL CURRENT DOWNLOADS
|
|
|
|
VideoDownloadManager.downloadFromResume(this, pkg, false)
|
|
|
|
}
|
2021-07-08 17:46:47 +00:00
|
|
|
|
2021-07-17 15:56:26 +00:00
|
|
|
// ADD QUEUE
|
|
|
|
// array needed because List gets cast exception to linkedList for some unknown reason
|
|
|
|
val resumeQueue =
|
|
|
|
getKey<Array<VideoDownloadManager.DownloadQueueResumePackage>>(VideoDownloadManager.KEY_RESUME_QUEUE_PACKAGES)
|
|
|
|
|
|
|
|
resumeQueue?.sortedBy { it.index }?.forEach {
|
|
|
|
VideoDownloadManager.downloadFromResume(this, it.pkg)
|
|
|
|
}
|
2021-07-25 14:25:09 +00:00
|
|
|
}*/
|
|
|
|
|
|
|
|
|
2021-06-14 00:00:29 +00:00
|
|
|
/*
|
|
|
|
val castContext = CastContext.getSharedInstance(applicationContext)
|
|
|
|
fun buildMediaQueueItem(video: String): MediaQueueItem {
|
|
|
|
// val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO)
|
|
|
|
//movieMetadata.putString(MediaMetadata.KEY_TITLE, "CloudStream")
|
|
|
|
val mediaInfo = MediaInfo.Builder(Uri.parse(video).toString())
|
|
|
|
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
|
|
|
|
.setContentType(MimeTypes.IMAGE_JPEG)
|
|
|
|
// .setMetadata(movieMetadata).build()
|
|
|
|
.build()
|
|
|
|
return MediaQueueItem.Builder(mediaInfo).build()
|
|
|
|
}*/
|
|
|
|
/*
|
|
|
|
castContext.addCastStateListener { state ->
|
|
|
|
if (state == CastState.CONNECTED) {
|
|
|
|
println("TESTING")
|
|
|
|
val isCasting = castContext?.sessionManager?.currentCastSession?.remoteMediaClient?.currentItem != null
|
|
|
|
if(!isCasting) {
|
|
|
|
val castPlayer = CastPlayer(castContext)
|
|
|
|
println("LOAD ITEM")
|
|
|
|
|
|
|
|
castPlayer.loadItem(buildMediaQueueItem("https://cdn.discordapp.com/attachments/551382684560261121/730169809408622702/ChromecastLogo6.png"),0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}*/
|
2021-07-31 22:11:56 +00:00
|
|
|
/*thread {
|
|
|
|
createISO()
|
|
|
|
}*/
|
2021-08-19 20:05:18 +00:00
|
|
|
|
2022-04-10 22:00:03 +00:00
|
|
|
if (BuildConfig.DEBUG) {
|
2023-07-30 03:05:13 +00:00
|
|
|
var providersAndroidManifestString = "Current androidmanifest should be:\n"
|
|
|
|
synchronized(allProviders) {
|
2023-07-27 19:47:42 +00:00
|
|
|
for (api in allProviders) {
|
|
|
|
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
|
|
|
|
api.mainUrl.removePrefix(
|
|
|
|
"https://"
|
|
|
|
)
|
|
|
|
}\" android:pathPrefix=\"/\"/>\n"
|
|
|
|
}
|
2022-04-10 22:00:03 +00:00
|
|
|
}
|
2023-07-30 03:05:13 +00:00
|
|
|
println(providersAndroidManifestString)
|
2022-04-10 22:00:03 +00:00
|
|
|
}
|
2021-09-30 12:16:23 +00:00
|
|
|
|
2021-07-30 01:14:53 +00:00
|
|
|
handleAppIntent(intent)
|
2021-08-15 17:38:41 +00:00
|
|
|
|
2022-08-20 17:39:04 +00:00
|
|
|
ioSafe {
|
2021-08-15 17:38:41 +00:00
|
|
|
runAutoUpdate()
|
|
|
|
}
|
2021-08-30 21:42:58 +00:00
|
|
|
|
2021-09-12 14:10:22 +00:00
|
|
|
APIRepository.dubStatusActive = getApiDubstatusSettings()
|
|
|
|
|
2022-01-07 19:27:25 +00:00
|
|
|
try {
|
|
|
|
// this ensures that no unnecessary space is taken
|
|
|
|
loadCache()
|
|
|
|
File(filesDir, "exoplayer").deleteRecursively() // old cache
|
|
|
|
File(cacheDir, "exoplayer").deleteOnExit() // current cache
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
}
|
2022-03-16 15:29:11 +00:00
|
|
|
println("Loaded everything")
|
2022-05-02 21:32:28 +00:00
|
|
|
|
|
|
|
ioSafe {
|
|
|
|
migrateResumeWatching()
|
|
|
|
}
|
2022-07-30 01:24:07 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
if (getKey(HAS_DONE_SETUP_KEY, false) != true) {
|
|
|
|
navController.navigate(R.id.navigation_setup_language)
|
2022-08-09 02:53:24 +00:00
|
|
|
// If no plugins bring up extensions screen
|
|
|
|
} else if (PluginManager.getPluginsOnline().isEmpty()
|
|
|
|
&& PluginManager.getPluginsLocal().isEmpty()
|
2022-08-15 01:31:33 +00:00
|
|
|
// && PREBUILT_REPOSITORIES.isNotEmpty()
|
2022-08-09 02:53:24 +00:00
|
|
|
) {
|
2022-08-16 19:07:49 +00:00
|
|
|
navController.navigate(
|
|
|
|
R.id.navigation_setup_extensions,
|
|
|
|
SetupFragmentExtensions.newInstance(false)
|
|
|
|
)
|
2022-07-30 01:24:07 +00:00
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
} finally {
|
|
|
|
setKey(HAS_DONE_SETUP_KEY, true)
|
|
|
|
}
|
2022-08-16 19:07:49 +00:00
|
|
|
|
|
|
|
// Used to check current focus for TV
|
|
|
|
// main {
|
|
|
|
// while (true) {
|
2023-01-16 22:49:59 +00:00
|
|
|
// delay(5000)
|
2022-08-16 19:07:49 +00:00
|
|
|
// println("Current focus: $currentFocus")
|
2023-01-16 22:49:59 +00:00
|
|
|
// showToast(this, currentFocus.toString(), Toast.LENGTH_LONG)
|
2022-08-16 19:07:49 +00:00
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2021-04-30 17:20:15 +00:00
|
|
|
}
|
2023-02-21 17:43:35 +00:00
|
|
|
|
|
|
|
suspend fun checkGithubConnectivity(): Boolean {
|
|
|
|
return try {
|
2023-03-10 20:33:13 +00:00
|
|
|
app.get(
|
|
|
|
"https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck",
|
|
|
|
timeout = 5
|
|
|
|
).text.trim() == "ok"
|
2023-02-21 17:43:35 +00:00
|
|
|
} catch (t: Throwable) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2022-04-11 20:17:34 +00:00
|
|
|
}
|