idk anymore tbh

This commit is contained in:
LagradOst 2021-05-23 00:25:56 +02:00
parent 7b81ec01b5
commit 8a49401dd0
15 changed files with 607 additions and 152 deletions

View file

@ -68,4 +68,10 @@ dependencies {
implementation 'jp.wasabeef:glide-transformations:4.0.0' implementation 'jp.wasabeef:glide-transformations:4.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.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"
} }

View file

@ -24,7 +24,7 @@ object APIHolder {
ShiroProvider() ShiroProvider()
) )
fun getApiFromName(apiName: String): MainAPI { fun getApiFromName(apiName: String?): MainAPI {
for (api in apis) { for (api in apis) {
if (apiName == api.name) if (apiName == api.name)
return api 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 // 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 return false
} }
} }
@ -81,8 +81,8 @@ enum class ShowStatus {
} }
enum class DubStatus { enum class DubStatus {
HasDub, Dubbed,
HasSub, Subbed,
} }
enum class TvType { enum class TvType {
@ -148,6 +148,7 @@ interface LoadResponse {
val type: TvType val type: TvType
val posterUrl: String? val posterUrl: String?
val year: Int? val year: Int?
val plot: String?
} }
data class AnimeLoadResponse( data class AnimeLoadResponse(
@ -165,8 +166,8 @@ data class AnimeLoadResponse(
val subEpisodes: ArrayList<Any>?, val subEpisodes: ArrayList<Any>?,
val showStatus: ShowStatus?, val showStatus: ShowStatus?,
override val plot: String?,
val tags: ArrayList<String>?, val tags: ArrayList<String>?,
val plot: String?,
val synonyms: ArrayList<String>?, val synonyms: ArrayList<String>?,
val malId: Int?, val malId: Int?,
@ -182,6 +183,7 @@ data class MovieLoadResponse(
override val posterUrl: String?, override val posterUrl: String?,
override val year: Int?, override val year: Int?,
override val plot: String?,
val imdbId: Int?, val imdbId: Int?,
) : LoadResponse ) : LoadResponse
@ -195,6 +197,7 @@ data class TvSeriesLoadResponse(
override val posterUrl: String?, override val posterUrl: String?,
override val year: Int?, override val year: Int?,
override val plot: String?,
val showStatus: ShowStatus?, val showStatus: ShowStatus?,
val imdbId: Int?, val imdbId: Int?,

View file

@ -3,13 +3,23 @@ package com.lagradost.cloudstream3
import android.os.Bundle import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.lagradost.cloudstream3.UIHelper.checkWrite import com.lagradost.cloudstream3.UIHelper.checkWrite
import com.lagradost.cloudstream3.UIHelper.requestRW 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 { private fun AppCompatActivity.backPressed(): Boolean {
val currentFragment = supportFragmentManager.fragments.last { val currentFragment = supportFragmentManager.fragments.last {
it.isVisible it.isVisible

View file

@ -36,7 +36,7 @@ object UIHelper {
this.runOnUiThread { this.runOnUiThread {
this.supportFragmentManager.beginTransaction() this.supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit) .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() .commit()
} }
} }

View file

@ -149,9 +149,9 @@ class ShiroProvider : MainAPI() {
val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java) val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java)
if (isDubbed) if (isDubbed)
set.add(DubStatus.HasDub) set.add(DubStatus.Dubbed)
else else
set.add(DubStatus.HasSub) set.add(DubStatus.Subbed)
val episodeCount = i.episodeCount.toInt() val episodeCount = i.episodeCount.toInt()
returnValue.add(AnimeSearchResponse( returnValue.add(AnimeSearchResponse(

View file

@ -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)
}
}

View file

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@ -24,14 +25,15 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.lagradost.cloudstream3.AnimeLoadResponse import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.checkWrite import com.lagradost.cloudstream3.UIHelper.checkWrite
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.loadResult
import com.lagradost.cloudstream3.UIHelper.requestRW import com.lagradost.cloudstream3.UIHelper.requestRW
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe 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 com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
@ -46,18 +48,22 @@ data class ResultEpisode(
val data: Any, val data: Any,
val apiName: String, val apiName: String,
val id: Int, val id: Int,
val index: Int,
val watchProgress: Float, // 0-1 val watchProgress: Float, // 0-1
) )
class ResultFragment : Fragment() { class ResultFragment : Fragment() {
fun newInstance(url: String, slug: String, apiName: String) = companion object {
ResultFragment().apply { fun newInstance(url: String, slug: String, apiName: String) =
arguments = Bundle().apply { ResultFragment().apply {
putString("url", url) arguments = Bundle().apply {
putString("slug", slug) putString("url", url)
putString("apiName", apiName) putString("slug", slug)
putString("apiName", apiName)
}
} }
} }
private lateinit var viewModel: ResultViewModel private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
@ -68,11 +74,15 @@ class ResultFragment : Fragment() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View? { ): View? {
viewModel = viewModel =
ViewModelProvider(this).get(ResultViewModel::class.java) ViewModelProvider(requireActivity()).get(ResultViewModel::class.java)
return inflater.inflate(R.layout.fragment_result, container, false) return inflater.inflate(R.layout.fragment_result, container, false)
} }
override fun onDestroy() {
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
super.onDestroy()
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -100,6 +110,11 @@ class ResultFragment : Fragment() {
allEpisodes = it allEpisodes = it
} }
observe(viewModel.episodes) { episodes ->
(result_episodes.adapter as EpisodeAdapter).cardList = episodes
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
}
observe(viewModel.resultResponse) { data -> observe(viewModel.resultResponse) { data ->
when (data) { when (data) {
is Resource.Success -> { 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 (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) { if (activity?.checkWrite() != true) {
val sfile: Unit = Android.Net.Uri.FromFile(subFile) activity?.requestRW()
vlcIntent.PutExtra("subtitles_location", sfile.Path) if (activity?.checkWrite() == true) return
//vlcIntent.PutExtra("sub_mrl", "file://" sfile.Path); }
//vlcIntent.PutExtra("subtitles_location", "file:///storage/emulated/0/Download/mirrorlist.srt");
}*/
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 val FROM_START = -1
activity?.startActivityForResult(vlcIntent, REQUEST_CODE) 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, result_episodes,
) { episodeClick -> ) { episodeClick ->
val id = episodeClick.data.id val id = episodeClick.data.id
when (episodeClick.action) { val index = episodeClick.data.index
ACTION_PLAY_EPISODE -> { val buildInPlayer = true
if (allEpisodes.containsKey(id)) { if (buildInPlayer) {
playEpisode(allEpisodes[id]) (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
} else { .setCustomAnimations(R.anim.enter_anim,
viewModel.loadEpisode(episodeClick.data) { res -> R.anim.exit_anim,
if (res is Resource.Success) { R.anim.pop_enter,
playEpisode(allEpisodes[id]) 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 ->
ACTION_RELOAD_EPISODE -> viewModel.loadEpisode(episodeClick.data) { res -> if (res is Resource.Success) {
if (res is Resource.Success) { playEpisode(allEpisodes[id], index)
playEpisode(allEpisodes[id]) }
} }*/
} }
} }
} }
@ -224,47 +247,31 @@ class ResultFragment : Fragment() {
result_episodes.adapter = adapter result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1) result_episodes.layoutManager = GridLayoutManager(context, 1)
if (d is AnimeLoadResponse) { if (d.plot != null) {
val preferEnglish = true var syno = d.plot!!
val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name if (syno.length > MAX_SYNO_LENGH) {
result_title.text = titleName syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
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"
} }
result_descript.setOnClickListener {
result_tags.text = (d.tags ?: ArrayList()).joinToString(separator = " | ") val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0 builder.setMessage(d.plot).setTitle("Synopsis")
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes) .show()
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.text = syno
} else { } 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
} }
} }
} }

View file

@ -4,9 +4,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -14,31 +13,110 @@ import kotlinx.coroutines.launch
class ResultViewModel : ViewModel() { class ResultViewModel : ViewModel() {
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData() private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
private val _episodes: MutableLiveData<ArrayList<ResultEpisode>> = MutableLiveData()
val resultResponse: LiveData<Resource<Any?>> get() = _resultResponse 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 { fun load(url: String, apiName: String) = viewModelScope.launch {
_apiName.postValue(apiName)
val data = safeApiCall { val data = safeApiCall {
getApiFromName(apiName).load(url) getApiFromName(apiName).load(url)
} }
_resultResponse.postValue(data) _resultResponse.postValue(data)
}
private val _allEpisodes: MutableLiveData<HashMap<Int, ArrayList<ExtractorLink>>> = MutableLiveData(HashMap()) when (data) {
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes 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)
}
} }

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.search package com.lagradost.cloudstream3.ui.search
import android.app.Activity import android.app.Activity
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,7 +8,6 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl 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.loadResult
import com.lagradost.cloudstream3.UIHelper.toPx import com.lagradost.cloudstream3.UIHelper.toPx
import com.lagradost.cloudstream3.ui.AutofitRecyclerView 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.backgroundCard
import kotlinx.android.synthetic.main.search_result_compact.view.imageText import kotlinx.android.synthetic.main.search_result_compact.view.imageText
import kotlinx.android.synthetic.main.search_result_compact.view.imageView import kotlinx.android.synthetic.main.search_result_compact.view.imageView
@ -117,10 +114,10 @@ class SearchAdapter(
is AnimeSearchResponse -> { is AnimeSearchResponse -> {
if (card.dubStatus?.size == 1) { if (card.dubStatus?.size == 1) {
//search_result_lang?.visibility = View.VISIBLE //search_result_lang?.visibility = View.VISIBLE
if (card.dubStatus.contains(DubStatus.HasDub)) { if (card.dubStatus.contains(DubStatus.Dubbed)) {
text_is_dub?.visibility = View.VISIBLE text_is_dub?.visibility = View.VISIBLE
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor)) //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)) //search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
text_is_sub?.visibility = View.VISIBLE text_is_sub?.visibility = View.VISIBLE
} }

View file

@ -126,8 +126,5 @@ class SearchFragment : Fragment() {
search_exit_icon.alpha = 1f search_exit_icon.alpha = 1f
search_loading_bar.alpha = 0f search_loading_bar.alpha = 0f
} }
main_search.onActionViewExpanded()
} }
} }

View file

@ -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)
}
}

View 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>

View 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>

View file

@ -7,6 +7,7 @@
<color name="colorOngoing">#F53B66</color> <!--FF8181--> <color name="colorOngoing">#F53B66</color> <!--FF8181-->
<color name="colorPrimaryDark">#3700B3</color> <color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#3b65f5</color> <!-- 818fff--> <color name="colorAccent">#3b65f5</color> <!-- 818fff-->
<color name="video_ripple">#80FFFFFF</color>
<color name="darkBackground">#2B2C30</color> <!--0f0f10 0E0E10 303135 2B2C30--> <color name="darkBackground">#2B2C30</color> <!--0f0f10 0E0E10 303135 2B2C30-->
<color name="bitDarkerGrayBackground">#1C1C20</color> <!--191a1f 19181E 202125 1C1C20--> <color name="bitDarkerGrayBackground">#1C1C20</color> <!--191a1f 19181E 202125 1C1C20-->

View file

@ -12,4 +12,5 @@
<string name="shadow_descript">Shadow</string> <string name="shadow_descript">Shadow</string>
<string name="episode_more_options_descript">More Options</string> <string name="episode_more_options_descript">More Options</string>
<string name="episode_play_descript">Play Episode</string> <string name="episode_play_descript">Play Episode</string>
<string name="go_back">Go back</string>
</resources> </resources>