diff --git a/app/build.gradle b/app/build.gradle index 3db3ad96..a871388b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 919ccbd2..cddfea90 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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?, val showStatus: ShowStatus?, + override val plot: String?, val tags: ArrayList?, - val plot: String?, val synonyms: ArrayList?, 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?, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7eda817a..7dd77387 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt index 466e07a5..28be0612 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt @@ -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() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt index 240bafd7..13ccf444 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ShiroProvider.kt @@ -149,9 +149,9 @@ class ShiroProvider : MainAPI() { val set: EnumSet = 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( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt new file mode 100644 index 00000000..4b60db60 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -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> = HashMap() + private var episodes: ArrayList = 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 649c2bf3..c595fe68 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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> = 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?) { + fun playEpisode(data: ArrayList?, 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() - 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 } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index 255a397e..98e4a004 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -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> = MutableLiveData() + private val _episodes: MutableLiveData> = MutableLiveData() val resultResponse: LiveData> get() = _resultResponse + val episodes: LiveData> get() = _episodes + private val dubStatus: MutableLiveData = 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>> = MutableLiveData(HashMap()) - val allEpisodes: LiveData>> 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() + 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() + 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) -> Unit) = viewModelScope.launch { - if (_allEpisodes.value?.contains(episode.id) == true) { - _allEpisodes.value?.remove(episode.id) - } - val links = ArrayList() - 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>> = + MutableLiveData(HashMap()) // LOOKUP BY ID + + val allEpisodes: LiveData>> get() = _allEpisodes + + private lateinit var _apiName: MutableLiveData + + fun loadEpisode(episode: ResultEpisode, callback: (Resource) -> Unit) { + loadEpisode(episode.id, episode.data, callback) + } + + fun loadEpisode(id: Int, data: Any, callback: (Resource) -> Unit) = + viewModelScope.launch { + if (_allEpisodes.value?.contains(id) == true) { + _allEpisodes.value?.remove(id) + } + val links = ArrayList() + 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) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index cd741a4f..703ba153 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -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 } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 672da1d3..9753bc6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -126,8 +126,5 @@ class SearchFragment : Fragment() { search_exit_icon.alpha = 1f search_loading_bar.alpha = 0f } - - main_search.onActionViewExpanded() - } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt new file mode 100644 index 00000000..c0722ba2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -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 { + 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 Context.setKey(path: String, value: T) { + val editor: SharedPreferences.Editor = getSharedPrefs().edit() + editor.putString(path, mapper.writeValueAsString(value)) + editor.apply() + } + + fun Context.setKey(folder: String, path: String, value: T) { + setKey(getFolderName(folder, path), value) + } + + inline fun String.toKotlinObject(): T { + return mapper.readValue(this, T::class.java) + } + + // GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR + inline fun 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 Context.getKey(path: String): T? { + return getKey(path, null) + } + + inline fun Context.getKey(folder: String, path: String): T? { + return getKey(getFolderName(folder, path), null) + } + + inline fun Context.getKey(folder: String, path: String, defVal: T?): T? { + return getKey(getFolderName(folder, path), defVal) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/video_tap_button.xml b/app/src/main/res/drawable/video_tap_button.xml new file mode 100644 index 00000000..af93740c --- /dev/null +++ b/app/src/main/res/drawable/video_tap_button.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml new file mode 100644 index 00000000..bf511fad --- /dev/null +++ b/app/src/main/res/layout/fragment_player.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b41676dd..ebf51a5a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,6 +7,7 @@ #F53B66 #3700B3 #3b65f5 + #80FFFFFF #2B2C30 #1C1C20 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f712e22f..58969e65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,4 +12,5 @@ Shadow More Options Play Episode + Go back \ No newline at end of file