forked from recloudstream/cloudstream
idk anymore tbh
This commit is contained in:
parent
7b81ec01b5
commit
8a49401dd0
15 changed files with 607 additions and 152 deletions
|
@ -68,4 +68,10 @@ dependencies {
|
|||
implementation 'jp.wasabeef:glide-transformations:4.0.0'
|
||||
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
||||
|
||||
// Exoplayer
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.14.0'
|
||||
implementation 'com.google.android.exoplayer:extension-cast:2.14.0'
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:2.14.0"
|
||||
implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
|
||||
}
|
|
@ -24,7 +24,7 @@ object APIHolder {
|
|||
ShiroProvider()
|
||||
)
|
||||
|
||||
fun getApiFromName(apiName: String): MainAPI {
|
||||
fun getApiFromName(apiName: String?): MainAPI {
|
||||
for (api in apis) {
|
||||
if (apiName == api.name)
|
||||
return api
|
||||
|
@ -53,7 +53,7 @@ abstract class MainAPI {
|
|||
}
|
||||
|
||||
// callback is fired once a link is found, will return true if method is executed successfully
|
||||
open fun loadLinks(data: Any, isCasting : Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
open fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ enum class ShowStatus {
|
|||
}
|
||||
|
||||
enum class DubStatus {
|
||||
HasDub,
|
||||
HasSub,
|
||||
Dubbed,
|
||||
Subbed,
|
||||
}
|
||||
|
||||
enum class TvType {
|
||||
|
@ -148,6 +148,7 @@ interface LoadResponse {
|
|||
val type: TvType
|
||||
val posterUrl: String?
|
||||
val year: Int?
|
||||
val plot: String?
|
||||
}
|
||||
|
||||
data class AnimeLoadResponse(
|
||||
|
@ -165,8 +166,8 @@ data class AnimeLoadResponse(
|
|||
val subEpisodes: ArrayList<Any>?,
|
||||
val showStatus: ShowStatus?,
|
||||
|
||||
override val plot: String?,
|
||||
val tags: ArrayList<String>?,
|
||||
val plot: String?,
|
||||
val synonyms: ArrayList<String>?,
|
||||
|
||||
val malId: Int?,
|
||||
|
@ -182,6 +183,7 @@ data class MovieLoadResponse(
|
|||
|
||||
override val posterUrl: String?,
|
||||
override val year: Int?,
|
||||
override val plot: String?,
|
||||
|
||||
val imdbId: Int?,
|
||||
) : LoadResponse
|
||||
|
@ -195,6 +197,7 @@ data class TvSeriesLoadResponse(
|
|||
|
||||
override val posterUrl: String?,
|
||||
override val year: Int?,
|
||||
override val plot: String?,
|
||||
|
||||
val showStatus: ShowStatus?,
|
||||
val imdbId: Int?,
|
||||
|
|
|
@ -3,13 +3,23 @@ package com.lagradost.cloudstream3
|
|||
import android.os.Bundle
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.lagradost.cloudstream3.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.UIHelper.requestRW
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
class MainActivity : AppCompatActivity() {/*, ViewModelStoreOwner {
|
||||
private val appViewModelStore: ViewModelStore by lazy {
|
||||
ViewModelStore()
|
||||
}
|
||||
|
||||
override fun getViewModelStore(): ViewModelStore {
|
||||
return appViewModelStore
|
||||
}*/
|
||||
|
||||
private fun AppCompatActivity.backPressed(): Boolean {
|
||||
val currentFragment = supportFragmentManager.fragments.last {
|
||||
it.isVisible
|
||||
|
|
|
@ -36,7 +36,7 @@ object UIHelper {
|
|||
this.runOnUiThread {
|
||||
this.supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
|
||||
.add(R.id.homeRoot, ResultFragment().newInstance(url, slug, apiName))
|
||||
.add(R.id.homeRoot, ResultFragment.newInstance(url, slug, apiName))
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,9 +149,9 @@ class ShiroProvider : MainAPI() {
|
|||
val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java)
|
||||
|
||||
if (isDubbed)
|
||||
set.add(DubStatus.HasDub)
|
||||
set.add(DubStatus.Dubbed)
|
||||
else
|
||||
set.add(DubStatus.HasSub)
|
||||
set.add(DubStatus.Subbed)
|
||||
val episodeCount = i.episodeCount.toInt()
|
||||
|
||||
returnValue.add(AnimeSearchResponse(
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AlphaAnimation
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||
import com.lagradost.cloudstream3.ui.result.ResultViewModel
|
||||
import com.lagradost.cloudstream3.utils.DataStore
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
|
||||
const val STATE_RESUME_WINDOW = "resumeWindow"
|
||||
const val STATE_RESUME_POSITION = "resumePosition"
|
||||
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
|
||||
const val STATE_PLAYER_PLAYING = "playerOnPlay"
|
||||
const val ACTION_MEDIA_CONTROL = "media_control"
|
||||
const val EXTRA_CONTROL_TYPE = "control_type"
|
||||
const val PLAYBACK_SPEED = "playback_speed"
|
||||
const val RESIZE_MODE_KEY = "resize_mode" // Last used resize mode
|
||||
const val PLAYBACK_SPEED_KEY = "playback_speed" // Last used playback speed
|
||||
|
||||
enum class PlayerEventType(val value: Int) {
|
||||
Stop(-1),
|
||||
Pause(0),
|
||||
Play(1),
|
||||
SeekForward(2),
|
||||
SeekBack(3),
|
||||
SkipCurrentChapter(4),
|
||||
NextEpisode(5),
|
||||
PlayPauseToggle(6)
|
||||
}
|
||||
|
||||
/*
|
||||
data class PlayerData(
|
||||
val id: Int, // UNIQUE IDENTIFIER, USED FOR SET TIME, HASH OF slug+episodeIndex
|
||||
val titleName: String, // TITLE NAME
|
||||
val episodeName: String?, // EPISODE NAME, NULL IF MOVIE
|
||||
val episodeIndex: Int?, // EPISODE INDEX, NULL IF MOVIE
|
||||
val seasonIndex : Int?, // SEASON INDEX IF IT IS FOUND, EPISODE CAN BE GIVEN BUT SEASON IS NOT GUARANTEED
|
||||
val episodes : Int?, // MAX EPISODE
|
||||
//val seasons : Int?, // SAME AS SEASON INDEX, NOT GUARANTEED, SET TO 1
|
||||
)*/
|
||||
data class PlayerData(
|
||||
val episodeIndex: Int,
|
||||
)
|
||||
|
||||
class PlayerFragment : Fragment() {
|
||||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||
|
||||
private var isFullscreen = false
|
||||
private var isPlayerPlaying = true
|
||||
private lateinit var viewModel: ResultViewModel
|
||||
private lateinit var playerData: PlayerData
|
||||
private var isLoading = true
|
||||
private lateinit var exoPlayer: SimpleExoPlayer
|
||||
|
||||
private fun seekTime(time: Long) {
|
||||
exoPlayer.seekTo(maxOf(minOf(exoPlayer.currentPosition + time, exoPlayer.duration), 0))
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
if (this::exoPlayer.isInitialized) {
|
||||
exoPlayer.release()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
if (this::exoPlayer.isInitialized) {
|
||||
outState.putInt(STATE_RESUME_WINDOW, exoPlayer.currentWindowIndex)
|
||||
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition)
|
||||
}
|
||||
outState.putBoolean(STATE_PLAYER_FULLSCREEN, isFullscreen)
|
||||
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
|
||||
outState.putInt(RESIZE_MODE_KEY, resizeMode)
|
||||
outState.putFloat(PLAYBACK_SPEED, playbackSpeed)
|
||||
savePos()
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(data: PlayerData) =
|
||||
PlayerFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
//println(data)
|
||||
putString("data", mapper.writeValueAsString(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePos() {
|
||||
if (this::exoPlayer.isInitialized) {
|
||||
/*if (
|
||||
&& exoPlayer.duration > 0 && exoPlayer.currentPosition > 0
|
||||
) {
|
||||
setViewPosDur(data!!, exoPlayer.currentPosition, exoPlayer.duration)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private val resizeModes = listOf(
|
||||
AspectRatioFrameLayout.RESIZE_MODE_FIT,
|
||||
AspectRatioFrameLayout.RESIZE_MODE_FILL,
|
||||
AspectRatioFrameLayout.RESIZE_MODE_ZOOM,
|
||||
)
|
||||
|
||||
private var resizeMode = 0
|
||||
private var playbackSpeed = 0f
|
||||
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
|
||||
private var episodes: ArrayList<ResultEpisode> = ArrayList()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
resizeMode = requireContext().getKey(RESIZE_MODE_KEY, 0)!!
|
||||
playbackSpeed = requireContext().getKey(PLAYBACK_SPEED_KEY, 1f)!!
|
||||
|
||||
observe(viewModel.episodes) { _episodes ->
|
||||
episodes = _episodes
|
||||
if (isLoading) {
|
||||
if (playerData.episodeIndex > 0 && playerData.episodeIndex < episodes.size) {
|
||||
|
||||
}
|
||||
else {
|
||||
// WHAT THE FUCK DID YOU DO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.allEpisodes) { _allEpisodes ->
|
||||
allEpisodes = _allEpisodes
|
||||
}
|
||||
|
||||
observe(viewModel.resultResponse) { data ->
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
val d = data.value
|
||||
if (d is LoadResponse) {
|
||||
|
||||
}
|
||||
}
|
||||
is Resource.Failure -> {
|
||||
//WTF, HOW DID YOU EVEN GET HERE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println(allEpisodes)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
viewModel = ViewModelProvider(requireActivity()).get(ResultViewModel::class.java)
|
||||
arguments?.getString("data")?.let {
|
||||
playerData = mapper.readValue(it, PlayerData::class.java)
|
||||
}
|
||||
return inflater.inflate(R.layout.fragment_player, container, false)
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
|
@ -24,14 +25,15 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.request.RequestOptions.bitmapTransform
|
||||
import com.lagradost.cloudstream3.AnimeLoadResponse
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.UIHelper.loadResult
|
||||
import com.lagradost.cloudstream3.UIHelper.requestRW
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
|
@ -46,18 +48,22 @@ data class ResultEpisode(
|
|||
val data: Any,
|
||||
val apiName: String,
|
||||
val id: Int,
|
||||
val index: Int,
|
||||
val watchProgress: Float, // 0-1
|
||||
)
|
||||
|
||||
class ResultFragment : Fragment() {
|
||||
fun newInstance(url: String, slug: String, apiName: String) =
|
||||
ResultFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString("url", url)
|
||||
putString("slug", slug)
|
||||
putString("apiName", apiName)
|
||||
companion object {
|
||||
fun newInstance(url: String, slug: String, apiName: String) =
|
||||
ResultFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString("url", url)
|
||||
putString("slug", slug)
|
||||
putString("apiName", apiName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lateinit var viewModel: ResultViewModel
|
||||
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
|
||||
|
@ -68,11 +74,15 @@ class ResultFragment : Fragment() {
|
|||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
viewModel =
|
||||
ViewModelProvider(this).get(ResultViewModel::class.java)
|
||||
|
||||
ViewModelProvider(requireActivity()).get(ResultViewModel::class.java)
|
||||
return inflater.inflate(R.layout.fragment_result, container, false)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -100,6 +110,11 @@ class ResultFragment : Fragment() {
|
|||
allEpisodes = it
|
||||
}
|
||||
|
||||
observe(viewModel.episodes) { episodes ->
|
||||
(result_episodes.adapter as EpisodeAdapter).cardList = episodes
|
||||
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
|
||||
}
|
||||
|
||||
observe(viewModel.resultResponse) { data ->
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
|
@ -130,66 +145,60 @@ class ResultFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
fun playEpisode(data: ArrayList<ExtractorLink>?) {
|
||||
fun playEpisode(data: ArrayList<ExtractorLink>?, episodeIndex: Int) {
|
||||
if (data != null) {
|
||||
if (activity?.checkWrite() != true) {
|
||||
activity?.requestRW()
|
||||
if (activity?.checkWrite() == true) return
|
||||
}
|
||||
|
||||
val outputDir = context!!.cacheDir // context being the Activity pointer
|
||||
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
|
||||
var text = "#EXTM3U";
|
||||
for (d in data.sortedBy { -it.quality }) {
|
||||
text += "\n#EXTINF:, ${d.name}\n${d.url}"
|
||||
}
|
||||
outputFile.writeText(text)
|
||||
val VLC_PACKAGE = "org.videolan.vlc"
|
||||
val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
|
||||
val VLC_COMPONENT: ComponentName =
|
||||
ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
|
||||
val REQUEST_CODE = 42
|
||||
|
||||
val FROM_START = -1
|
||||
val FROM_PROGRESS = -2
|
||||
|
||||
|
||||
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
|
||||
|
||||
vlcIntent.setPackage(VLC_PACKAGE)
|
||||
// vlcIntent.setDataAndTypeAndNormalize(outputFile.toUri(), "video/*")
|
||||
vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
vlcIntent.setDataAndType(FileProvider.getUriForFile(activity!!,
|
||||
activity!!.applicationContext.packageName + ".provider",
|
||||
outputFile), "video/*")
|
||||
|
||||
val startId = FROM_PROGRESS
|
||||
|
||||
var position = startId
|
||||
if (startId == FROM_START) {
|
||||
position = 1
|
||||
} else if (startId == FROM_PROGRESS) {
|
||||
position = 0
|
||||
}
|
||||
|
||||
vlcIntent.putExtra("position", position)
|
||||
//vlcIntent.putExtra("title", episodeName)
|
||||
/*
|
||||
if (subFile != null) {
|
||||
val sfile: Unit = Android.Net.Uri.FromFile(subFile)
|
||||
vlcIntent.PutExtra("subtitles_location", sfile.Path)
|
||||
//vlcIntent.PutExtra("sub_mrl", "file://" sfile.Path);
|
||||
//vlcIntent.PutExtra("subtitles_location", "file:///storage/emulated/0/Download/mirrorlist.srt");
|
||||
}*/
|
||||
if (activity?.checkWrite() != true) {
|
||||
activity?.requestRW()
|
||||
if (activity?.checkWrite() == true) return
|
||||
}
|
||||
|
||||
vlcIntent.setComponent(VLC_COMPONENT)
|
||||
val outputDir = context!!.cacheDir
|
||||
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
|
||||
var text = "#EXTM3U";
|
||||
for (link in data.sortedBy { -it.quality }) {
|
||||
text += "\n#EXTINF:, ${link.name}\n${link.url}"
|
||||
}
|
||||
outputFile.writeText(text)
|
||||
val VLC_PACKAGE = "org.videolan.vlc"
|
||||
val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
|
||||
val VLC_COMPONENT: ComponentName =
|
||||
ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
|
||||
val REQUEST_CODE = 42
|
||||
|
||||
//lastId = episodeId
|
||||
activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
|
||||
val FROM_START = -1
|
||||
val FROM_PROGRESS = -2
|
||||
|
||||
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
|
||||
|
||||
vlcIntent.setPackage(VLC_PACKAGE)
|
||||
vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
|
||||
vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
||||
vlcIntent.setDataAndType(FileProvider.getUriForFile(activity!!,
|
||||
activity!!.applicationContext.packageName + ".provider",
|
||||
outputFile), "video/*")
|
||||
|
||||
val startId = FROM_PROGRESS
|
||||
|
||||
var position = startId
|
||||
if (startId == FROM_START) {
|
||||
position = 1
|
||||
} else if (startId == FROM_PROGRESS) {
|
||||
position = 0
|
||||
}
|
||||
|
||||
vlcIntent.putExtra("position", position)
|
||||
//vlcIntent.putExtra("title", episodeName)
|
||||
|
||||
vlcIntent.setComponent(VLC_COMPONENT)
|
||||
|
||||
activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
|
||||
*/
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,22 +209,36 @@ class ResultFragment : Fragment() {
|
|||
result_episodes,
|
||||
) { episodeClick ->
|
||||
val id = episodeClick.data.id
|
||||
when (episodeClick.action) {
|
||||
ACTION_PLAY_EPISODE -> {
|
||||
if (allEpisodes.containsKey(id)) {
|
||||
playEpisode(allEpisodes[id])
|
||||
} else {
|
||||
viewModel.loadEpisode(episodeClick.data) { res ->
|
||||
if (res is Resource.Success) {
|
||||
playEpisode(allEpisodes[id])
|
||||
val index = episodeClick.data.index
|
||||
val buildInPlayer = true
|
||||
if (buildInPlayer) {
|
||||
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.enter_anim,
|
||||
R.anim.exit_anim,
|
||||
R.anim.pop_enter,
|
||||
R.anim.pop_exit)
|
||||
.add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(index)))
|
||||
.commit()
|
||||
} else {
|
||||
when (episodeClick.action) {
|
||||
|
||||
/*
|
||||
ACTION_PLAY_EPISODE -> {
|
||||
if (allEpisodes.containsKey(id)) {
|
||||
playEpisode(allEpisodes[id], index)
|
||||
} else {
|
||||
viewModel.loadEpisode(episodeClick.data) { res ->
|
||||
if (res is Resource.Success) {
|
||||
playEpisode(allEpisodes[id], index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res ->
|
||||
if (res is Resource.Success) {
|
||||
playEpisode(allEpisodes[id])
|
||||
}
|
||||
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res ->
|
||||
if (res is Resource.Success) {
|
||||
playEpisode(allEpisodes[id], index)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -224,47 +247,31 @@ class ResultFragment : Fragment() {
|
|||
result_episodes.adapter = adapter
|
||||
result_episodes.layoutManager = GridLayoutManager(context, 1)
|
||||
|
||||
if (d is AnimeLoadResponse) {
|
||||
val preferEnglish = true
|
||||
val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
|
||||
result_title.text = titleName
|
||||
result_toolbar.title = titleName
|
||||
|
||||
if (d.plot != null) {
|
||||
var syno = d.plot
|
||||
if (syno.length > MAX_SYNO_LENGH) {
|
||||
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
|
||||
}
|
||||
result_descript.setOnClickListener {
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
|
||||
builder.setMessage(d.plot).setTitle("Synopsis")
|
||||
.show()
|
||||
}
|
||||
result_descript.text = syno
|
||||
} else {
|
||||
result_descript.text = "No Plot found"
|
||||
if (d.plot != null) {
|
||||
var syno = d.plot!!
|
||||
if (syno.length > MAX_SYNO_LENGH) {
|
||||
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
|
||||
}
|
||||
|
||||
result_tags.text = (d.tags ?: ArrayList()).joinToString(separator = " | ")
|
||||
val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0
|
||||
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes)
|
||||
if (dataList != null && apiName != null) {
|
||||
val episodes = ArrayList<ResultEpisode>()
|
||||
for ((index, i) in dataList.withIndex()) {
|
||||
episodes.add(ResultEpisode(
|
||||
null, // TODO ADD NAMES
|
||||
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
|
||||
i,
|
||||
apiName,
|
||||
(slug + index).hashCode(),
|
||||
0f,//(index * 0.1f),//TODO TEST; REMOVE
|
||||
))
|
||||
}
|
||||
(result_episodes.adapter as EpisodeAdapter).cardList = episodes
|
||||
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
|
||||
result_descript.setOnClickListener {
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
|
||||
builder.setMessage(d.plot).setTitle("Synopsis")
|
||||
.show()
|
||||
}
|
||||
result_descript.text = syno
|
||||
} else {
|
||||
result_title.text = d.name
|
||||
result_descript.text = "No Plot found"
|
||||
}
|
||||
|
||||
when (d) {
|
||||
is AnimeLoadResponse -> {
|
||||
val preferEnglish = true
|
||||
val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
|
||||
result_title.text = titleName
|
||||
result_toolbar.title = titleName
|
||||
|
||||
result_tags.text = (d.tags ?: ArrayList()).joinToString(separator = " | ")
|
||||
}
|
||||
else -> result_title.text = d.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
@ -14,31 +13,110 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class ResultViewModel : ViewModel() {
|
||||
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
|
||||
private val _episodes: MutableLiveData<ArrayList<ResultEpisode>> = MutableLiveData()
|
||||
val resultResponse: LiveData<Resource<Any?>> get() = _resultResponse
|
||||
val episodes: LiveData<ArrayList<ResultEpisode>> get() = _episodes
|
||||
private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
|
||||
|
||||
fun load(url: String, apiName: String) = viewModelScope.launch {
|
||||
_apiName.postValue(apiName)
|
||||
val data = safeApiCall {
|
||||
getApiFromName(apiName).load(url)
|
||||
}
|
||||
|
||||
_resultResponse.postValue(data)
|
||||
}
|
||||
|
||||
private val _allEpisodes: MutableLiveData<HashMap<Int, ArrayList<ExtractorLink>>> = MutableLiveData(HashMap())
|
||||
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
val d = data.value
|
||||
if (d is LoadResponse) {
|
||||
when (d) {
|
||||
is AnimeLoadResponse -> {
|
||||
val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0
|
||||
dubStatus.postValue(if (isDub) DubStatus.Dubbed else DubStatus.Subbed)
|
||||
|
||||
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes)
|
||||
|
||||
if (dataList != null) {
|
||||
val episodes = ArrayList<ResultEpisode>()
|
||||
for ((index, i) in dataList.withIndex()) {
|
||||
episodes.add(ResultEpisode(
|
||||
null, // TODO ADD NAMES
|
||||
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
|
||||
i,
|
||||
apiName,
|
||||
(d.url + index).hashCode(),
|
||||
index,
|
||||
0f,//(index * 0.1f),//TODO TEST; REMOVE
|
||||
))
|
||||
}
|
||||
_episodes.postValue(episodes)
|
||||
}
|
||||
|
||||
}
|
||||
is TvSeriesLoadResponse -> {
|
||||
val episodes = ArrayList<ResultEpisode>()
|
||||
for ((index, i) in d.episodes.withIndex()) {
|
||||
episodes.add(ResultEpisode(
|
||||
null, // TODO ADD NAMES
|
||||
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
|
||||
i,
|
||||
apiName,
|
||||
(d.url + index).hashCode(),
|
||||
index,
|
||||
0f,//(index * 0.1f),//TODO TEST; REMOVE
|
||||
))
|
||||
}
|
||||
_episodes.postValue(episodes)
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
_episodes.postValue(arrayListOf(ResultEpisode(null,
|
||||
0,
|
||||
d.movieUrl,
|
||||
d.apiName,
|
||||
(d.url).hashCode(),
|
||||
0,
|
||||
0f)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else -> {
|
||||
|
||||
fun loadEpisode(episode: ResultEpisode, callback: (Resource<Boolean>) -> Unit) = viewModelScope.launch {
|
||||
if (_allEpisodes.value?.contains(episode.id) == true) {
|
||||
_allEpisodes.value?.remove(episode.id)
|
||||
}
|
||||
val links = ArrayList<ExtractorLink>()
|
||||
val data = safeApiCall {
|
||||
getApiFromName(episode.apiName).loadLinks(episode.data, true) { //TODO IMPLEMENT CASTING
|
||||
links.add(it)
|
||||
_allEpisodes.value?.set(episode.id, links)
|
||||
// _allEpisodes.value?.get(episode.id)?.add(it)
|
||||
}
|
||||
}
|
||||
callback.invoke(data)
|
||||
}
|
||||
|
||||
private val _allEpisodes: MutableLiveData<HashMap<Int, ArrayList<ExtractorLink>>> =
|
||||
MutableLiveData(HashMap()) // LOOKUP BY ID
|
||||
|
||||
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes
|
||||
|
||||
private lateinit var _apiName: MutableLiveData<String>
|
||||
|
||||
fun loadEpisode(episode: ResultEpisode, callback: (Resource<Boolean>) -> Unit) {
|
||||
loadEpisode(episode.id, episode.data, callback)
|
||||
}
|
||||
|
||||
fun loadEpisode(id: Int, data: Any, callback: (Resource<Boolean>) -> Unit) =
|
||||
viewModelScope.launch {
|
||||
if (_allEpisodes.value?.contains(id) == true) {
|
||||
_allEpisodes.value?.remove(id)
|
||||
}
|
||||
val links = ArrayList<ExtractorLink>()
|
||||
val data = safeApiCall {
|
||||
getApiFromName(_apiName.value).loadLinks(data, true) { //TODO IMPLEMENT CASTING
|
||||
links.add(it)
|
||||
_allEpisodes.value?.set(id, links)
|
||||
// _allEpisodes.value?.get(episode.id)?.add(it)
|
||||
}
|
||||
}
|
||||
callback.invoke(data)
|
||||
}
|
||||
|
||||
fun loadIndex(index: Int): ResultEpisode? {
|
||||
return episodes.value?.get(index)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package com.lagradost.cloudstream3.ui.search
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -9,7 +8,6 @@ import android.widget.FrameLayout
|
|||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
|
@ -19,7 +17,6 @@ import com.lagradost.cloudstream3.UIHelper.getGridIsCompact
|
|||
import com.lagradost.cloudstream3.UIHelper.loadResult
|
||||
import com.lagradost.cloudstream3.UIHelper.toPx
|
||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
import kotlinx.android.synthetic.main.search_result_compact.view.*
|
||||
import kotlinx.android.synthetic.main.search_result_compact.view.backgroundCard
|
||||
import kotlinx.android.synthetic.main.search_result_compact.view.imageText
|
||||
import kotlinx.android.synthetic.main.search_result_compact.view.imageView
|
||||
|
@ -117,10 +114,10 @@ class SearchAdapter(
|
|||
is AnimeSearchResponse -> {
|
||||
if (card.dubStatus?.size == 1) {
|
||||
//search_result_lang?.visibility = View.VISIBLE
|
||||
if (card.dubStatus.contains(DubStatus.HasDub)) {
|
||||
if (card.dubStatus.contains(DubStatus.Dubbed)) {
|
||||
text_is_dub?.visibility = View.VISIBLE
|
||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
|
||||
} else if (card.dubStatus.contains(DubStatus.HasSub)) {
|
||||
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
|
||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
|
||||
text_is_sub?.visibility = View.VISIBLE
|
||||
}
|
||||
|
|
|
@ -126,8 +126,5 @@ class SearchFragment : Fragment() {
|
|||
search_exit_icon.alpha = 1f
|
||||
search_loading_bar.alpha = 0f
|
||||
}
|
||||
|
||||
main_search.onActionViewExpanded()
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
|
||||
const val PREFERENCES_NAME: String = "rebuild_preference"
|
||||
|
||||
object DataStore {
|
||||
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||
|
||||
private fun getPreferences(context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun Context.getSharedPrefs(): SharedPreferences {
|
||||
return getPreferences(this)
|
||||
}
|
||||
|
||||
fun getFolderName(folder: String, path: String): String {
|
||||
return "${folder}/${path}"
|
||||
}
|
||||
|
||||
fun Context.getKeys(folder: String): List<String> {
|
||||
return this.getSharedPrefs().all.keys.filter { it.startsWith(folder) }
|
||||
}
|
||||
|
||||
fun Context.removeKey(folder: String, path: String) {
|
||||
removeKey(getFolderName(folder, path))
|
||||
}
|
||||
|
||||
fun Context.containsKey(folder: String, path: String): Boolean {
|
||||
return containsKey(getFolderName(folder, path))
|
||||
}
|
||||
|
||||
fun Context.containsKey(path: String): Boolean {
|
||||
val prefs = getSharedPrefs()
|
||||
return prefs.contains(path)
|
||||
}
|
||||
|
||||
fun Context.removeKey(path: String) {
|
||||
val prefs = getSharedPrefs()
|
||||
if (prefs.contains(path)) {
|
||||
val editor: SharedPreferences.Editor = prefs.edit()
|
||||
editor.remove(path)
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.removeKeys(folder: String): Int {
|
||||
val keys = getKeys(folder)
|
||||
keys.forEach { value ->
|
||||
removeKey(value)
|
||||
}
|
||||
return keys.size
|
||||
}
|
||||
|
||||
fun <T> Context.setKey(path: String, value: T) {
|
||||
val editor: SharedPreferences.Editor = getSharedPrefs().edit()
|
||||
editor.putString(path, mapper.writeValueAsString(value))
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun <T> Context.setKey(folder: String, path: String, value: T) {
|
||||
setKey(getFolderName(folder, path), value)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> String.toKotlinObject(): T {
|
||||
return mapper.readValue(this, T::class.java)
|
||||
}
|
||||
|
||||
// GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR
|
||||
inline fun <reified T : Any> Context.getKey(path: String, defVal: T?): T? {
|
||||
try {
|
||||
val json: String = getSharedPrefs().getString(path, null) ?: return defVal
|
||||
return json.toKotlinObject()
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Context.getKey(path: String): T? {
|
||||
return getKey(path, null)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Context.getKey(folder: String, path: String): T? {
|
||||
return getKey(getFolderName(folder, path), null)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Context.getKey(folder: String, path: String, defVal: T?): T? {
|
||||
return getKey(getFolderName(folder, path), defVal)
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/video_tap_button.xml
Normal file
10
app/src/main/res/drawable/video_tap_button.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/video_ripple">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/video_ripple"/>
|
||||
</shape>
|
||||
<color android:color="@color/video_ripple"/>
|
||||
</item>
|
||||
</ripple>
|
76
app/src/main/res/layout/fragment_player.xml
Normal file
76
app/src/main/res/layout/fragment_player.xml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:keepScreenOn="true"
|
||||
app:backgroundTint="@android:color/black"
|
||||
android:background="@android:color/black"
|
||||
android:screenOrientation="userLandscape"
|
||||
app:surface_type="texture_view"
|
||||
>
|
||||
<!-- app:controller_layout_id="@layout/player_custom_layout"-->
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
app:show_timeout="0"
|
||||
app:hide_on_touch="false"
|
||||
app:auto_show="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fastforward_increment="10000"
|
||||
app:rewind_increment="10000"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
<FrameLayout
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/loading_overlay"
|
||||
android:background="@android:color/black"
|
||||
android:backgroundTint="@android:color/black"
|
||||
>
|
||||
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:id="@+id/main_load"
|
||||
>
|
||||
</ProgressBar>
|
||||
<FrameLayout
|
||||
android:layout_margin="5dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
android:contentDescription="@string/go_back">
|
||||
</ImageView>
|
||||
<ImageView
|
||||
android:id="@+id/video_go_back_holder"
|
||||
android:layout_width="65dp"
|
||||
android:layout_height="65dp"
|
||||
android:layout_gravity="center"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="@drawable/video_tap_button"
|
||||
android:contentDescription="@string/go_back">
|
||||
</ImageView>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,6 +7,7 @@
|
|||
<color name="colorOngoing">#F53B66</color> <!--FF8181-->
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#3b65f5</color> <!-- 818fff-->
|
||||
<color name="video_ripple">#80FFFFFF</color>
|
||||
|
||||
<color name="darkBackground">#2B2C30</color> <!--0f0f10 0E0E10 303135 2B2C30-->
|
||||
<color name="bitDarkerGrayBackground">#1C1C20</color> <!--191a1f 19181E 202125 1C1C20-->
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
<string name="shadow_descript">Shadow</string>
|
||||
<string name="episode_more_options_descript">More Options</string>
|
||||
<string name="episode_play_descript">Play Episode</string>
|
||||
<string name="go_back">Go back</string>
|
||||
</resources>
|
Loading…
Reference in a new issue