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 '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()
)
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?,

View File

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

View File

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

View File

@ -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(

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.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
}
}
}

View File

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

View File

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

View File

@ -126,8 +126,5 @@ class SearchFragment : Fragment() {
search_exit_icon.alpha = 1f
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="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-->

View File

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