forked from recloudstream/cloudstream
resume watching and stuff
This commit is contained in:
parent
7cfb19c39b
commit
15d85422ca
21 changed files with 553 additions and 192 deletions
|
@ -234,11 +234,10 @@ class HomePageList(
|
||||||
|
|
||||||
interface SearchResponse {
|
interface SearchResponse {
|
||||||
val name: String
|
val name: String
|
||||||
val url: String // PUBLIC URL FOR OPEN IN APP
|
val url: String
|
||||||
val apiName: String
|
val apiName: String
|
||||||
val type: TvType
|
val type: TvType
|
||||||
val posterUrl: String?
|
val posterUrl: String?
|
||||||
val year: Int?
|
|
||||||
val id: Int?
|
val id: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +248,7 @@ data class AnimeSearchResponse(
|
||||||
override val type: TvType,
|
override val type: TvType,
|
||||||
|
|
||||||
override val posterUrl: String?,
|
override val posterUrl: String?,
|
||||||
override val year: Int?,
|
val year: Int?,
|
||||||
|
|
||||||
val otherName: String?,
|
val otherName: String?,
|
||||||
val dubStatus: EnumSet<DubStatus>?,
|
val dubStatus: EnumSet<DubStatus>?,
|
||||||
|
@ -265,7 +264,7 @@ data class MovieSearchResponse(
|
||||||
override val type: TvType,
|
override val type: TvType,
|
||||||
|
|
||||||
override val posterUrl: String?,
|
override val posterUrl: String?,
|
||||||
override val year: Int?,
|
val year: Int?,
|
||||||
override val id: Int? = null,
|
override val id: Int? = null,
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
||||||
|
@ -276,7 +275,7 @@ data class TvSeriesSearchResponse(
|
||||||
override val type: TvType,
|
override val type: TvType,
|
||||||
|
|
||||||
override val posterUrl: String?,
|
override val posterUrl: String?,
|
||||||
override val year: Int?,
|
val year: Int?,
|
||||||
val episodes: Int?,
|
val episodes: Int?,
|
||||||
override val id: Int? = null,
|
override val id: Int? = null,
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
|
@ -88,6 +88,7 @@ object DownloadButtonSetup {
|
||||||
info.path.toString(),
|
info.path.toString(),
|
||||||
keyInfo.relativePath,
|
keyInfo.relativePath,
|
||||||
keyInfo.displayName,
|
keyInfo.displayName,
|
||||||
|
click.data.parentId,
|
||||||
click.data.id,
|
click.data.id,
|
||||||
headerName ?: "null",
|
headerName ?: "null",
|
||||||
if (click.data.episode <= 0) null else click.data.episode,
|
if (click.data.episode <= 0) null else click.data.episode,
|
||||||
|
|
|
@ -52,6 +52,7 @@ class DownloadViewModel : ViewModel() {
|
||||||
// parentId : downloadsCount
|
// parentId : downloadsCount
|
||||||
val totalDownloads = HashMap<Int, Int>()
|
val totalDownloads = HashMap<Int, Int>()
|
||||||
|
|
||||||
|
|
||||||
// Gets all children downloads
|
// Gets all children downloads
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
for (c in children) {
|
for (c in children) {
|
||||||
|
@ -67,9 +68,13 @@ class DownloadViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cached = withContext(Dispatchers.IO) {
|
val cached = withContext(Dispatchers.IO) { // wont fetch useless keys
|
||||||
val headers = context.getKeys(DOWNLOAD_HEADER_CACHE)
|
totalDownloads.entries.filter { it.value > 0 }.mapNotNull {
|
||||||
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadHeaderCached>(it) }
|
context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||||
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
it.key.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val visual = withContext(Dispatchers.IO) {
|
val visual = withContext(Dispatchers.IO) {
|
||||||
|
@ -78,10 +83,12 @@ class DownloadViewModel : ViewModel() {
|
||||||
val bytes = totalBytesUsedByChild[it.id] ?: 0
|
val bytes = totalBytesUsedByChild[it.id] ?: 0
|
||||||
val currentBytes = currentBytesUsedByChild[it.id] ?: 0
|
val currentBytes = currentBytesUsedByChild[it.id] ?: 0
|
||||||
if (bytes <= 0 || downloads <= 0) return@mapNotNull null
|
if (bytes <= 0 || downloads <= 0) return@mapNotNull null
|
||||||
val movieEpisode = if (!it.type.isMovieType()) null else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(
|
val movieEpisode =
|
||||||
DOWNLOAD_EPISODE_CACHE,
|
if (!it.type.isMovieType()) null
|
||||||
getFolderName(it.id.toString(), it.id.toString())
|
else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(
|
||||||
)
|
DOWNLOAD_EPISODE_CACHE,
|
||||||
|
getFolderName(it.id.toString(), it.id.toString())
|
||||||
|
)
|
||||||
VisualDownloadHeaderCached(
|
VisualDownloadHeaderCached(
|
||||||
0,
|
0,
|
||||||
downloads,
|
downloads,
|
||||||
|
@ -90,7 +97,9 @@ class DownloadViewModel : ViewModel() {
|
||||||
it,
|
it,
|
||||||
movieEpisode
|
movieEpisode
|
||||||
)
|
)
|
||||||
}
|
}.sortedBy {
|
||||||
|
(it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0)
|
||||||
|
} // episode sorting by episode, lowest to highest
|
||||||
}
|
}
|
||||||
|
|
||||||
val stat = StatFs(Environment.getExternalStorageDirectory().path)
|
val stat = StatFs(Environment.getExternalStorageDirectory().path)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.home_result_grid.view.*
|
import kotlinx.android.synthetic.main.home_result_grid.view.*
|
||||||
|
|
||||||
|
@ -44,60 +45,9 @@ class HomeChildItemAdapter(
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(itemView: View, private val clickCallback: (SearchClickCallback) -> Unit) :
|
constructor(itemView: View, private val clickCallback: (SearchClickCallback) -> Unit) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
val cardView: ImageView = itemView.imageView
|
|
||||||
private val cardText: TextView = itemView.imageText
|
|
||||||
private val textType: TextView? = itemView.text_type
|
|
||||||
// val search_result_lang: ImageView? = itemView.search_result_lang
|
|
||||||
|
|
||||||
private val textIsDub: View? = itemView.text_is_dub
|
|
||||||
private val textIsSub: View? = itemView.text_is_sub
|
|
||||||
|
|
||||||
//val cardTextExtra: TextView? = itemView.imageTextExtra
|
|
||||||
//val imageTextProvider: TextView? = itemView.imageTextProvider
|
|
||||||
private val bg: CardView = itemView.backgroundCard
|
|
||||||
|
|
||||||
fun bind(card: SearchResponse) {
|
fun bind(card: SearchResponse) {
|
||||||
textType?.text = when (card.type) {
|
SearchResultBuilder.bind(clickCallback, card, itemView)
|
||||||
TvType.Anime -> "Anime"
|
|
||||||
TvType.Movie -> "Movie"
|
|
||||||
TvType.AnimeMovie -> "Movie"
|
|
||||||
TvType.ONA -> "ONA"
|
|
||||||
TvType.TvSeries -> "TV"
|
|
||||||
TvType.Cartoon -> "Cartoon"
|
|
||||||
}
|
|
||||||
// search_result_lang?.visibility = View.GONE
|
|
||||||
|
|
||||||
textIsDub?.visibility = View.GONE
|
|
||||||
textIsSub?.visibility = View.GONE
|
|
||||||
|
|
||||||
cardText.text = card.name
|
|
||||||
|
|
||||||
//imageTextProvider.text = card.apiName
|
|
||||||
cardView.setImage(card.posterUrl)
|
|
||||||
|
|
||||||
bg.setOnClickListener {
|
|
||||||
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card))
|
|
||||||
}
|
|
||||||
|
|
||||||
bg.setOnLongClickListener {
|
|
||||||
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_SHOW_METADATA, it, card))
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
when (card) {
|
|
||||||
is AnimeSearchResponse -> {
|
|
||||||
if (card.dubStatus?.size == 1) {
|
|
||||||
//search_result_lang?.visibility = View.VISIBLE
|
|
||||||
if (card.dubStatus.contains(DubStatus.Dubbed)) {
|
|
||||||
textIsDub?.visibility = View.VISIBLE
|
|
||||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
|
|
||||||
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
|
|
||||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
|
|
||||||
textIsSub?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,13 @@ import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.*
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
||||||
|
@ -66,7 +66,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
recycle.adapter = SearchAdapter(item.list, recycle) { callback ->
|
recycle.adapter = SearchAdapter(item.list, recycle) { callback ->
|
||||||
handleSearchClickCallback(this, callback)
|
handleSearchClickCallback(this, callback)
|
||||||
if (callback.action == SEARCH_ACTION_LOAD) {
|
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
|
||||||
bottomSheetDialogBuilder.dismiss()
|
bottomSheetDialogBuilder.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ class HomeFragment : Fragment() {
|
||||||
while (random?.posterUrl == null) {
|
while (random?.posterUrl == null) {
|
||||||
try {
|
try {
|
||||||
random = home.items.random().list.random()
|
random = home.items.random().list.random()
|
||||||
} catch (e : Exception) {
|
} catch (e: Exception) {
|
||||||
// probs Collection is empty.
|
// probs Collection is empty.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ class HomeFragment : Fragment() {
|
||||||
val validAPIs = apis.filter { api -> api.hasMainPage }
|
val validAPIs = apis.filter { api -> api.hasMainPage }
|
||||||
|
|
||||||
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) {
|
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) {
|
||||||
homeViewModel.load(validAPIs[itemId])
|
homeViewModel.loadAndCancel(validAPIs[itemId])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +196,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private fun reloadStored() {
|
private fun reloadStored() {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
|
homeViewModel.loadResumeWatching(ctx)
|
||||||
homeViewModel.loadStoredData(ctx, WatchType.fromInternalId(ctx.getKey(HOME_BOOKMARK_VALUE)))
|
homeViewModel.loadStoredData(ctx, WatchType.fromInternalId(ctx.getKey(HOME_BOOKMARK_VALUE)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,6 +236,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
home_change_api.setOnClickListener(apiChangeClickListener)
|
home_change_api.setOnClickListener(apiChangeClickListener)
|
||||||
|
home_change_api_loading.setOnClickListener(apiChangeClickListener)
|
||||||
|
|
||||||
observe(homeViewModel.apiName) {
|
observe(homeViewModel.apiName) {
|
||||||
context?.setKey(HOMEPAGE_API, it)
|
context?.setKey(HOMEPAGE_API, it)
|
||||||
|
@ -326,6 +328,21 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observe(homeViewModel.resumeWatching) { resumeWatching ->
|
||||||
|
home_watch_holder.visibility = if (resumeWatching.isNotEmpty()) View.VISIBLE else View.GONE
|
||||||
|
(home_watch_child_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList = resumeWatching
|
||||||
|
home_watch_child_recyclerview?.adapter?.notifyDataSetChanged()
|
||||||
|
|
||||||
|
home_watch_child_more_info.setOnClickListener {
|
||||||
|
activity?.loadHomepageList(
|
||||||
|
HomePageList(
|
||||||
|
home_watch_parent_item_title?.text?.toString() ?: getString(R.string.continue_watching),
|
||||||
|
resumeWatching
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter(ArrayList()) { callback ->
|
home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter(ArrayList()) { callback ->
|
||||||
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
||||||
val id = callback.card.id
|
val id = callback.card.id
|
||||||
|
@ -342,12 +359,43 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
home_watch_child_recyclerview.adapter = HomeChildItemAdapter(ArrayList()) { callback ->
|
||||||
|
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
||||||
|
val id = callback.card.id
|
||||||
|
if (id != null) {
|
||||||
|
callback.view.popupMenuNoIcons(
|
||||||
|
listOf(
|
||||||
|
Pair(1, R.string.action_open_watching),
|
||||||
|
Pair(0, R.string.action_remove_watching)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (itemId == 1) {
|
||||||
|
handleSearchClickCallback(
|
||||||
|
activity,
|
||||||
|
SearchClickCallback(SEARCH_ACTION_LOAD, callback.view, callback.card)
|
||||||
|
)
|
||||||
|
reloadStored()
|
||||||
|
}
|
||||||
|
if (itemId == 0) {
|
||||||
|
val card = callback.card
|
||||||
|
if(card is DataStoreHelper.ResumeWatchingResult) {
|
||||||
|
context?.removeLastWatched(card.parentId)
|
||||||
|
reloadStored()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleSearchClickCallback(activity, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(home_root)
|
context?.fixPaddingStatusbar(home_root)
|
||||||
|
|
||||||
home_master_recycler.adapter = adapter
|
home_master_recycler.adapter = adapter
|
||||||
home_master_recycler.layoutManager = GridLayoutManager(context, 1)
|
home_master_recycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
reloadStored()
|
reloadStored()
|
||||||
homeViewModel.load(context?.getKey<String>(HOMEPAGE_API))
|
homeViewModel.loadAndCancel(context?.getKey<String>(HOMEPAGE_API))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,10 +13,18 @@ import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ -38,6 +46,44 @@ class HomeViewModel : ViewModel() {
|
||||||
private val _bookmarks = MutableLiveData<List<SearchResponse>>()
|
private val _bookmarks = MutableLiveData<List<SearchResponse>>()
|
||||||
val bookmarks: LiveData<List<SearchResponse>> = _bookmarks
|
val bookmarks: LiveData<List<SearchResponse>> = _bookmarks
|
||||||
|
|
||||||
|
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
||||||
|
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||||
|
|
||||||
|
fun loadResumeWatching(context: Context) = viewModelScope.launch {
|
||||||
|
val resumeWatching = withContext(Dispatchers.IO) {
|
||||||
|
context.getAllResumeStateIds().mapNotNull { id ->
|
||||||
|
context.getLastWatched(id)
|
||||||
|
}.sortedBy { -it.updateTime }
|
||||||
|
}
|
||||||
|
|
||||||
|
// val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>()
|
||||||
|
|
||||||
|
val resumeWatchingResult = withContext(Dispatchers.IO) {
|
||||||
|
resumeWatching.map { resume ->
|
||||||
|
val data = context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||||
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
resume.parentId.toString()
|
||||||
|
) ?: return@map null
|
||||||
|
val watchPos = context.getViewPos(resume.episodeId)
|
||||||
|
DataStoreHelper.ResumeWatchingResult(
|
||||||
|
data.name,
|
||||||
|
data.url,
|
||||||
|
data.apiName,
|
||||||
|
data.type,
|
||||||
|
data.poster,
|
||||||
|
watchPos,
|
||||||
|
resume.episodeId,
|
||||||
|
resume.parentId,
|
||||||
|
resume.episode,
|
||||||
|
resume.season,
|
||||||
|
resume.isFromDownload
|
||||||
|
)
|
||||||
|
}.filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
_resumeWatching.postValue(resumeWatchingResult)
|
||||||
|
}
|
||||||
|
|
||||||
fun loadStoredData(context: Context, preferredWatchStatus: WatchType?) = viewModelScope.launch {
|
fun loadStoredData(context: Context, preferredWatchStatus: WatchType?) = viewModelScope.launch {
|
||||||
val watchStatusIds = withContext(Dispatchers.IO) {
|
val watchStatusIds = withContext(Dispatchers.IO) {
|
||||||
context.getAllWatchStateIds().map { id ->
|
context.getAllWatchStateIds().map { id ->
|
||||||
|
@ -78,19 +124,26 @@ class HomeViewModel : ViewModel() {
|
||||||
_bookmarks.postValue(list)
|
_bookmarks.postValue(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(api: MainAPI?) = viewModelScope.launch {
|
var onGoingLoad: Job? = null
|
||||||
|
fun loadAndCancel(api: MainAPI?) {
|
||||||
|
onGoingLoad?.cancel()
|
||||||
|
onGoingLoad = load(api)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun load(api: MainAPI?) = viewModelScope.launch {
|
||||||
repo = if (api?.hasMainPage == true) {
|
repo = if (api?.hasMainPage == true) {
|
||||||
APIRepository(api)
|
APIRepository(api)
|
||||||
} else {
|
} else {
|
||||||
autoloadRepo()
|
autoloadRepo()
|
||||||
}
|
}
|
||||||
|
|
||||||
_apiName.postValue(repo?.name)
|
_apiName.postValue(repo?.name)
|
||||||
_page.postValue(Resource.Loading())
|
_page.postValue(Resource.Loading())
|
||||||
_page.postValue(repo?.getMainPage())
|
_page.postValue(repo?.getMainPage())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(preferredApiName: String?) = viewModelScope.launch {
|
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch {
|
||||||
val api = getApiFromNameNull(preferredApiName)
|
val api = getApiFromNameNull(preferredApiName)
|
||||||
load(api)
|
loadAndCancel(api)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -77,6 +77,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
|
||||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setLastWatched
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
|
@ -146,6 +147,7 @@ data class UriData(
|
||||||
val uri: String,
|
val uri: String,
|
||||||
val relativePath: String,
|
val relativePath: String,
|
||||||
val displayName: String,
|
val displayName: String,
|
||||||
|
val parentId: Int?,
|
||||||
val id: Int?,
|
val id: Int?,
|
||||||
val name: String,
|
val name: String,
|
||||||
val episode: Int?,
|
val episode: Int?,
|
||||||
|
@ -652,12 +654,24 @@ class PlayerFragment : Fragment() {
|
||||||
if (this::exoPlayer.isInitialized) {
|
if (this::exoPlayer.isInitialized) {
|
||||||
if (exoPlayer.duration > 0 && exoPlayer.currentPosition > 0) {
|
if (exoPlayer.duration > 0 && exoPlayer.currentPosition > 0) {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
ctx.setViewPos(
|
if (this::viewModel.isInitialized) {
|
||||||
if (isDownloadedFile) uriData.id else getEpisode()?.id,
|
viewModel.setViewPos(
|
||||||
exoPlayer.currentPosition,
|
ctx,
|
||||||
exoPlayer.duration
|
if (isDownloadedFile) uriData.id else getEpisode()?.id,
|
||||||
)
|
exoPlayer.currentPosition,
|
||||||
if (!isDownloadedFile)
|
exoPlayer.duration
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ctx.setViewPos(
|
||||||
|
if (isDownloadedFile) uriData.id else getEpisode()?.id,
|
||||||
|
exoPlayer.currentPosition,
|
||||||
|
exoPlayer.duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloadedFile) {
|
||||||
|
ctx.setLastWatched(uriData.parentId, uriData.id, uriData.episode, uriData.season, true)
|
||||||
|
} else
|
||||||
viewModel.reloadEpisodes(ctx)
|
viewModel.reloadEpisodes(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,9 @@ const val MAX_SYNO_LENGH = 300
|
||||||
|
|
||||||
const val START_ACTION_NORMAL = 0
|
const val START_ACTION_NORMAL = 0
|
||||||
const val START_ACTION_RESUME_LATEST = 1
|
const val START_ACTION_RESUME_LATEST = 1
|
||||||
|
const val START_ACTION_LOAD_EP = 2
|
||||||
|
|
||||||
|
const val START_VALUE_NORMAL = 0
|
||||||
|
|
||||||
data class ResultEpisode(
|
data class ResultEpisode(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
@ -140,12 +143,13 @@ fun ResultEpisode.getWatchProgress(): Float {
|
||||||
|
|
||||||
class ResultFragment : Fragment() {
|
class ResultFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(url: String, apiName: String, startAction: Int = 0) =
|
fun newInstance(url: String, apiName: String, startAction: Int = 0, startValue : Int = 0) =
|
||||||
ResultFragment().apply {
|
ResultFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putString("url", url)
|
putString("url", url)
|
||||||
putString("apiName", apiName)
|
putString("apiName", apiName)
|
||||||
putInt("startAction", startAction)
|
putInt("startAction", startAction)
|
||||||
|
putInt("startValue", startValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,6 +235,7 @@ class ResultFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var startAction: Int? = null
|
var startAction: Int? = null
|
||||||
|
var startValue: Int? = null
|
||||||
|
|
||||||
private fun lateFixDownloadButton(show: Boolean) {
|
private fun lateFixDownloadButton(show: Boolean) {
|
||||||
if (!show || currentType?.isMovieType() == false) {
|
if (!show || currentType?.isMovieType() == false) {
|
||||||
|
@ -267,6 +272,7 @@ class ResultFragment : Fragment() {
|
||||||
url = arguments?.getString("url")
|
url = arguments?.getString("url")
|
||||||
val apiName = arguments?.getString("apiName") ?: return
|
val apiName = arguments?.getString("apiName") ?: return
|
||||||
startAction = arguments?.getInt("startAction") ?: START_ACTION_NORMAL
|
startAction = arguments?.getInt("startAction") ?: START_ACTION_NORMAL
|
||||||
|
startValue = arguments?.getInt("startValue") ?: START_VALUE_NORMAL
|
||||||
|
|
||||||
val api = getApiFromName(apiName)
|
val api = getApiFromName(apiName)
|
||||||
if (media_route_button != null) {
|
if (media_route_button != null) {
|
||||||
|
@ -443,7 +449,8 @@ class ResultFragment : Fragment() {
|
||||||
|
|
||||||
// SET VISUAL KEYS
|
// SET VISUAL KEYS
|
||||||
ctx.setKey(
|
ctx.setKey(
|
||||||
DOWNLOAD_HEADER_CACHE, parentId.toString(),
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
parentId.toString(),
|
||||||
VideoDownloadHelper.DownloadHeaderCached(
|
VideoDownloadHelper.DownloadHeaderCached(
|
||||||
apiName,
|
apiName,
|
||||||
url ?: return@let,
|
url ?: return@let,
|
||||||
|
@ -749,13 +756,21 @@ class ResultFragment : Fragment() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep))
|
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep))
|
||||||
startAction = null
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
START_ACTION_LOAD_EP -> {
|
||||||
|
for (ep in episodeList) {
|
||||||
|
if (ep.id == startValue) { // watched too much
|
||||||
|
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
startAction = null
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.allEpisodes) {
|
observe(viewModel.allEpisodes) {
|
||||||
|
|
|
@ -9,18 +9,25 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setBookmarkedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setBookmarkedData
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setLastWatched
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
const val EPISODE_RANGE_SIZE = 50
|
const val EPISODE_RANGE_SIZE = 50
|
||||||
const val EPISODE_RANGE_OVERLOAD = 60
|
const val EPISODE_RANGE_OVERLOAD = 60
|
||||||
|
@ -30,6 +37,8 @@ class ResultViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
|
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
|
||||||
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||||
|
private val episodeById: MutableLiveData<HashMap<Int, Int>> = MutableLiveData() // lookup by ID to get Index
|
||||||
|
|
||||||
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||||
private val _publicEpisodesCount: MutableLiveData<Int> = MutableLiveData() // before the sorting
|
private val _publicEpisodesCount: MutableLiveData<Int> = MutableLiveData() // before the sorting
|
||||||
private val _rangeOptions: MutableLiveData<List<String>> = MutableLiveData()
|
private val _rangeOptions: MutableLiveData<List<String>> = MutableLiveData()
|
||||||
|
@ -96,7 +105,7 @@ class ResultViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
val seasons = seasonTypes.toList().map { it.first }
|
val seasons = seasonTypes.toList().map { it.first }
|
||||||
seasonSelections.postValue(seasons)
|
seasonSelections.postValue(seasons)
|
||||||
if(seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS
|
if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS
|
||||||
_publicEpisodes.postValue(ArrayList())
|
_publicEpisodes.postValue(ArrayList())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -141,7 +150,7 @@ class ResultViewModel : ViewModel() {
|
||||||
selectedRangeInt.postValue(realRange)
|
selectedRangeInt.postValue(realRange)
|
||||||
selectedRange.postValue(rangeList[realRange])
|
selectedRange.postValue(rangeList[realRange])
|
||||||
} else {
|
} else {
|
||||||
val allRange ="1-${currentList.size}"
|
val allRange = "1-${currentList.size}"
|
||||||
_rangeOptions.postValue(listOf(allRange))
|
_rangeOptions.postValue(listOf(allRange))
|
||||||
selectedRangeInt.postValue(0)
|
selectedRangeInt.postValue(0)
|
||||||
selectedRange.postValue(allRange)
|
selectedRange.postValue(allRange)
|
||||||
|
@ -160,6 +169,11 @@ class ResultViewModel : ViewModel() {
|
||||||
|
|
||||||
private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) {
|
private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) {
|
||||||
_episodes.postValue(list)
|
_episodes.postValue(list)
|
||||||
|
val set = HashMap<Int, Int>()
|
||||||
|
|
||||||
|
list.withIndex().forEach { set[it.value.id] = it.index }
|
||||||
|
episodeById.postValue(set)
|
||||||
|
|
||||||
filterEpisodes(
|
filterEpisodes(
|
||||||
context,
|
context,
|
||||||
list,
|
list,
|
||||||
|
@ -176,6 +190,40 @@ class ResultViewModel : ViewModel() {
|
||||||
updateEpisodes(context, null, copy, selectedSeason.value)
|
updateEpisodes(context, null, copy, selectedSeason.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setViewPos(context: Context?, episodeId: Int?, pos: Long, dur: Long) {
|
||||||
|
try {
|
||||||
|
if (context == null || episodeId == null) return
|
||||||
|
context.setViewPos(episodeId, pos, dur)
|
||||||
|
var index = episodeById.value?.get(episodeId) ?: return
|
||||||
|
|
||||||
|
var startPos = pos
|
||||||
|
var startDur = dur
|
||||||
|
val episodeList = (episodes.value ?: return)
|
||||||
|
var episode = episodeList[index]
|
||||||
|
val parentId = id.value ?: return
|
||||||
|
while (true) {
|
||||||
|
if (startDur > 0L && (startPos * 100 / startDur) > 95) {
|
||||||
|
index++
|
||||||
|
if (episodeList.size <= index) { // last episode
|
||||||
|
context.removeLastWatched(parentId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
episode = episodeList[index]
|
||||||
|
|
||||||
|
startPos = episode.position
|
||||||
|
startDur = episode.duration
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
context.setLastWatched(parentId, episode.id, episode.episode, episode.season)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun load(context: Context, url: String, apiName: String) = viewModelScope.launch {
|
fun load(context: Context, url: String, apiName: String) = viewModelScope.launch {
|
||||||
_resultResponse.postValue(Resource.Loading(url))
|
_resultResponse.postValue(Resource.Loading(url))
|
||||||
|
|
||||||
|
@ -195,14 +243,28 @@ class ResultViewModel : ViewModel() {
|
||||||
id.postValue(mainId)
|
id.postValue(mainId)
|
||||||
loadWatchStatus(context, mainId)
|
loadWatchStatus(context, mainId)
|
||||||
|
|
||||||
|
context.setKey(
|
||||||
|
DOWNLOAD_HEADER_CACHE,
|
||||||
|
mainId.toString(),
|
||||||
|
VideoDownloadHelper.DownloadHeaderCached(
|
||||||
|
apiName,
|
||||||
|
url,
|
||||||
|
d.type,
|
||||||
|
d.name,
|
||||||
|
d.posterUrl,
|
||||||
|
mainId,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
when (d) {
|
when (d) {
|
||||||
is AnimeLoadResponse -> {
|
is AnimeLoadResponse -> {
|
||||||
val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0
|
val isDub = d.dubEpisodes != null && d.dubEpisodes.isNotEmpty()
|
||||||
dubStatus.postValue(if (isDub) DubStatus.Dubbed else DubStatus.Subbed)
|
dubStatus.postValue(if (isDub) DubStatus.Dubbed else DubStatus.Subbed)
|
||||||
|
|
||||||
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes)
|
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes)
|
||||||
|
|
||||||
if (dataList != null) {
|
if (dataList != null) { // TODO dub and sub at the same time
|
||||||
val episodes = ArrayList<ResultEpisode>()
|
val episodes = ArrayList<ResultEpisode>()
|
||||||
for ((index, i) in dataList.withIndex()) {
|
for ((index, i) in dataList.withIndex()) {
|
||||||
episodes.add(
|
episodes.add(
|
||||||
|
|
|
@ -28,6 +28,7 @@ import kotlin.math.roundToInt
|
||||||
|
|
||||||
const val SEARCH_ACTION_LOAD = 0
|
const val SEARCH_ACTION_LOAD = 0
|
||||||
const val SEARCH_ACTION_SHOW_METADATA = 1
|
const val SEARCH_ACTION_SHOW_METADATA = 1
|
||||||
|
const val SEARCH_ACTION_PLAY_FILE = 2
|
||||||
|
|
||||||
class SearchClickCallback(val action: Int, val view: View, val card: SearchResponse)
|
class SearchClickCallback(val action: Int, val view: View, val card: SearchResponse)
|
||||||
|
|
||||||
|
@ -66,16 +67,7 @@ class SearchAdapter(
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
val cardView: ImageView = itemView.imageView
|
val cardView: ImageView = itemView.imageView
|
||||||
private val cardText: TextView = itemView.imageText
|
|
||||||
private val textType: TextView? = itemView.text_type
|
|
||||||
// val search_result_lang: ImageView? = itemView.search_result_lang
|
|
||||||
|
|
||||||
private val textIsDub: View? = itemView.text_is_dub
|
|
||||||
private val textIsSub: View? = itemView.text_is_sub
|
|
||||||
|
|
||||||
//val cardTextExtra: TextView? = itemView.imageTextExtra
|
|
||||||
//val imageTextProvider: TextView? = itemView.imageTextProvider
|
|
||||||
private val bg: CardView = itemView.backgroundCard
|
|
||||||
private val compactView = itemView.context.getGridIsCompact()
|
private val compactView = itemView.context.getGridIsCompact()
|
||||||
private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
||||||
|
|
||||||
|
@ -89,47 +81,7 @@ class SearchAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textType?.text = when (card.type) {
|
SearchResultBuilder.bind(clickCallback, card, itemView)
|
||||||
TvType.Anime -> "Anime"
|
|
||||||
TvType.Movie -> "Movie"
|
|
||||||
TvType.AnimeMovie -> "Movie"
|
|
||||||
TvType.ONA -> "ONA"
|
|
||||||
TvType.TvSeries -> "TV"
|
|
||||||
TvType.Cartoon -> "Cartoon"
|
|
||||||
}
|
|
||||||
// search_result_lang?.visibility = View.GONE
|
|
||||||
|
|
||||||
textIsDub?.visibility = View.GONE
|
|
||||||
textIsSub?.visibility = View.GONE
|
|
||||||
|
|
||||||
cardText.text = card.name
|
|
||||||
|
|
||||||
//imageTextProvider.text = card.apiName
|
|
||||||
cardView.setImage(card.posterUrl)
|
|
||||||
|
|
||||||
bg.setOnClickListener {
|
|
||||||
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card))
|
|
||||||
}
|
|
||||||
|
|
||||||
bg.setOnLongClickListener {
|
|
||||||
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_SHOW_METADATA, it, card))
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
when (card) {
|
|
||||||
is AnimeSearchResponse -> {
|
|
||||||
if (card.dubStatus?.size == 1) {
|
|
||||||
//search_result_lang?.visibility = View.VISIBLE
|
|
||||||
if (card.dubStatus.contains(DubStatus.Dubbed)) {
|
|
||||||
textIsDub?.visibility = View.VISIBLE
|
|
||||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
|
|
||||||
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
|
|
||||||
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
|
|
||||||
textIsSub?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,7 +305,7 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
searchViewModel.search(query)
|
searchViewModel.searchAndCancel(query)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,14 @@ package com.lagradost.cloudstream3.ui.search
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP
|
||||||
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
|
||||||
object SearchHelper {
|
object SearchHelper {
|
||||||
fun handleSearchClickCallback(activity: Activity?, callback: SearchClickCallback) {
|
fun handleSearchClickCallback(activity: Activity?, callback: SearchClickCallback) {
|
||||||
|
@ -12,6 +19,32 @@ object SearchHelper {
|
||||||
SEARCH_ACTION_LOAD -> {
|
SEARCH_ACTION_LOAD -> {
|
||||||
activity.loadSearchResult(card)
|
activity.loadSearchResult(card)
|
||||||
}
|
}
|
||||||
|
SEARCH_ACTION_PLAY_FILE -> {
|
||||||
|
if (card is DataStoreHelper.ResumeWatchingResult && card.id != null) {
|
||||||
|
if (card.isFromDownload) {
|
||||||
|
handleDownloadClick(
|
||||||
|
activity, card.name, DownloadClickEvent(
|
||||||
|
DOWNLOAD_ACTION_PLAY_FILE,
|
||||||
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
|
card.name,
|
||||||
|
card.posterUrl,
|
||||||
|
card.episode ?: 0,
|
||||||
|
card.season,
|
||||||
|
card.id!!,
|
||||||
|
card.parentId ?: return,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
activity.loadSearchResult(card, START_ACTION_LOAD_EP, card.id!!)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleSearchClickCallback(activity, SearchClickCallback(SEARCH_ACTION_LOAD,callback.view,callback.card))
|
||||||
|
}
|
||||||
|
}
|
||||||
SEARCH_ACTION_SHOW_METADATA -> {
|
SEARCH_ACTION_SHOW_METADATA -> {
|
||||||
showToast(activity, callback.card.name, Toast.LENGTH_SHORT)
|
showToast(activity, callback.card.name, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.search
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import com.lagradost.cloudstream3.AnimeSearchResponse
|
||||||
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.isMovieType
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
import kotlinx.android.synthetic.main.home_result_grid.view.*
|
||||||
|
|
||||||
|
object SearchResultBuilder {
|
||||||
|
fun bind(
|
||||||
|
clickCallback: (SearchClickCallback) -> Unit,
|
||||||
|
card: SearchResponse,
|
||||||
|
itemView: View
|
||||||
|
) {
|
||||||
|
val cardView: ImageView = itemView.imageView
|
||||||
|
val cardText: TextView = itemView.imageText
|
||||||
|
|
||||||
|
val textIsDub: View? = itemView.text_is_dub
|
||||||
|
val textIsSub: View? = itemView.text_is_sub
|
||||||
|
|
||||||
|
val bg: CardView = itemView.backgroundCard
|
||||||
|
|
||||||
|
val bar: ProgressBar? = itemView.watchProgress
|
||||||
|
val playImg: ImageView? = itemView.search_item_download_play
|
||||||
|
|
||||||
|
// Do logic
|
||||||
|
|
||||||
|
bar?.visibility = View.GONE
|
||||||
|
playImg?.visibility = View.GONE
|
||||||
|
textIsDub?.visibility = View.GONE
|
||||||
|
textIsSub?.visibility = View.GONE
|
||||||
|
|
||||||
|
cardText.text = card.name
|
||||||
|
|
||||||
|
//imageTextProvider.text = card.apiName
|
||||||
|
cardView.setImage(card.posterUrl)
|
||||||
|
|
||||||
|
bg.setOnClickListener {
|
||||||
|
clickCallback.invoke(SearchClickCallback(if(card is DataStoreHelper.ResumeWatchingResult) SEARCH_ACTION_PLAY_FILE else SEARCH_ACTION_LOAD, it, card))
|
||||||
|
}
|
||||||
|
|
||||||
|
bg.setOnLongClickListener {
|
||||||
|
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_SHOW_METADATA, it, card))
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
when (card) {
|
||||||
|
is DataStoreHelper.ResumeWatchingResult -> {
|
||||||
|
val pos = card.watchPos?.fixVisual()
|
||||||
|
if (pos != null) {
|
||||||
|
bar?.max = (pos.duration / 1000).toInt()
|
||||||
|
bar?.progress = (pos.position / 1000).toInt()
|
||||||
|
bar?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
playImg?.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
if (!card.type.isMovieType()) {
|
||||||
|
cardText.text = getNameFull(card.name, card.episode, card.season)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AnimeSearchResponse -> {
|
||||||
|
if (card.dubStatus?.size == 1) {
|
||||||
|
//search_result_lang?.visibility = View.VISIBLE
|
||||||
|
if (card.dubStatus.contains(DubStatus.Dubbed)) {
|
||||||
|
textIsDub?.visibility = View.VISIBLE
|
||||||
|
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
|
||||||
|
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
|
||||||
|
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
|
||||||
|
textIsSub?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
data class OnGoingSearch(
|
data class OnGoingSearch(
|
||||||
|
@ -23,20 +24,24 @@ class SearchViewModel : ViewModel() {
|
||||||
private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData()
|
private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData()
|
||||||
val currentSearch: LiveData<ArrayList<OnGoingSearch>> get() = _currentSearch
|
val currentSearch: LiveData<ArrayList<OnGoingSearch>> get() = _currentSearch
|
||||||
|
|
||||||
var searchCounter = 0
|
|
||||||
private val repos = apis.map { APIRepository(it) }
|
private val repos = apis.map { APIRepository(it) }
|
||||||
|
|
||||||
private fun clearSearch() {
|
private fun clearSearch() {
|
||||||
_searchResponse.postValue(Resource.Success(ArrayList()))
|
_searchResponse.postValue(Resource.Success(ArrayList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String) = viewModelScope.launch {
|
var onGoingSearch : Job? = null
|
||||||
searchCounter++
|
fun searchAndCancel(query: String) {
|
||||||
|
onGoingSearch?.cancel()
|
||||||
|
onGoingSearch = search(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun search(query: String) = viewModelScope.launch {
|
||||||
if (query.length <= 1) {
|
if (query.length <= 1) {
|
||||||
clearSearch()
|
clearSearch()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val localSearchCounter = searchCounter
|
|
||||||
_searchResponse.postValue(Resource.Loading())
|
_searchResponse.postValue(Resource.Loading())
|
||||||
|
|
||||||
val currentList = ArrayList<OnGoingSearch>()
|
val currentList = ArrayList<OnGoingSearch>()
|
||||||
|
@ -47,35 +52,33 @@ class SearchViewModel : ViewModel() {
|
||||||
(providersActive.size == 0 || providersActive.contains(a.name))
|
(providersActive.size == 0 || providersActive.contains(a.name))
|
||||||
}.map { a ->
|
}.map { a ->
|
||||||
currentList.add(OnGoingSearch(a.name, a.search(query)))
|
currentList.add(OnGoingSearch(a.name, a.search(query)))
|
||||||
if (localSearchCounter == searchCounter) {
|
_currentSearch.postValue(currentList)
|
||||||
_currentSearch.postValue(currentList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_currentSearch.postValue(currentList)
|
_currentSearch.postValue(currentList)
|
||||||
|
|
||||||
if (localSearchCounter != searchCounter) return@launch
|
|
||||||
|
|
||||||
val list = ArrayList<SearchResponse>()
|
val list = ArrayList<SearchResponse>()
|
||||||
val nestedList = currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
|
val nestedList =
|
||||||
|
currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
|
||||||
|
|
||||||
// I do it this way to move the relevant search results to the top
|
// I do it this way to move the relevant search results to the top
|
||||||
var index = 0
|
var index = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
var added = 0
|
var added = 0
|
||||||
for (sublist in nestedList) {
|
for (sublist in nestedList) {
|
||||||
if(sublist.size > index) {
|
if (sublist.size > index) {
|
||||||
list.add(sublist[index])
|
list.add(sublist[index])
|
||||||
added++
|
added++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(added == 0) break
|
if (added == 0) break
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
|
|
||||||
_searchResponse.postValue(Resource.Success(list))
|
_searchResponse.postValue(Resource.Success(list))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun quickSearch(query: String) = viewModelScope.launch {
|
fun quickSearch(query: String) {
|
||||||
return@launch
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -50,39 +50,39 @@ object AppUtils {
|
||||||
* | Episode 2
|
* | Episode 2
|
||||||
* **/
|
* **/
|
||||||
fun getNameFull(name: String?, episode: Int?, season: Int?): String {
|
fun getNameFull(name: String?, episode: Int?, season: Int?): String {
|
||||||
val rEpisode = if(episode == 0) null else episode
|
val rEpisode = if (episode == 0) null else episode
|
||||||
val rSeason = if(season == 0) null else season
|
val rSeason = if (season == 0) null else season
|
||||||
|
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
return if(rEpisode != null && rSeason != null) {
|
return if (rEpisode != null && rSeason != null) {
|
||||||
"S${rSeason}:E${rEpisode} $name"
|
"S${rSeason}:E${rEpisode} $name"
|
||||||
} else if(rEpisode != null) {
|
} else if (rEpisode != null) {
|
||||||
"Episode $rEpisode. $name"
|
"Episode $rEpisode. $name"
|
||||||
} else {
|
} else {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(rEpisode != null && rSeason != null) {
|
if (rEpisode != null && rSeason != null) {
|
||||||
return "Season $rSeason - Episode $rEpisode"
|
return "Season $rSeason - Episode $rEpisode"
|
||||||
} else if(rSeason == null) {
|
} else if (rSeason == null) {
|
||||||
return "Episode $rEpisode"
|
return "Episode $rEpisode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppCompatActivity.loadResult(url: String, apiName: String, startAction: Int = 0) {
|
fun AppCompatActivity.loadResult(url: String, apiName: String, startAction: Int = 0, startValue: Int = 0) {
|
||||||
this.runOnUiThread {
|
this.runOnUiThread {
|
||||||
viewModelStore.clear()
|
viewModelStore.clear()
|
||||||
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, apiName, startAction))
|
.add(R.id.homeRoot, ResultFragment.newInstance(url, apiName, startAction, startValue))
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity?.loadSearchResult(card: SearchResponse, startAction: Int = 0) {
|
fun Activity?.loadSearchResult(card: SearchResponse, startAction: Int = 0, startValue: Int = 0) {
|
||||||
(this as AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction)
|
(this as AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
|
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
|
||||||
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
||||||
|
//const val WATCH_HEADER_CACHE = "watch_header_cache"
|
||||||
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
||||||
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
|
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
|
||||||
const val HOMEPAGE_API = "home_api_used"
|
const val HOMEPAGE_API = "home_api_used"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
const val VIDEO_POS_DUR = "video_pos_dur"
|
const val VIDEO_POS_DUR = "video_pos_dur"
|
||||||
const val RESULT_WATCH_STATE = "result_watch_state"
|
const val RESULT_WATCH_STATE = "result_watch_state"
|
||||||
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
||||||
|
const val RESULT_RESUME_WATCHING = "result_resume_watching"
|
||||||
const val RESULT_SEASON = "result_season"
|
const val RESULT_SEASON = "result_season"
|
||||||
|
|
||||||
object DataStoreHelper {
|
object DataStoreHelper {
|
||||||
|
@ -35,7 +36,23 @@ object DataStoreHelper {
|
||||||
override val apiName: String,
|
override val apiName: String,
|
||||||
override val type: TvType,
|
override val type: TvType,
|
||||||
override val posterUrl: String?,
|
override val posterUrl: String?,
|
||||||
override val year: Int?,
|
val year: Int?,
|
||||||
|
) : SearchResponse
|
||||||
|
|
||||||
|
data class ResumeWatchingResult(
|
||||||
|
override val name: String,
|
||||||
|
override val url: String,
|
||||||
|
override val apiName: String,
|
||||||
|
override val type: TvType,
|
||||||
|
override val posterUrl: String?,
|
||||||
|
|
||||||
|
val watchPos: PosDur?,
|
||||||
|
|
||||||
|
override val id: Int?,
|
||||||
|
val parentId: Int?,
|
||||||
|
val episode: Int?,
|
||||||
|
val season: Int?,
|
||||||
|
val isFromDownload: Boolean,
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
||||||
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
||||||
|
@ -47,6 +64,48 @@ object DataStoreHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.getAllResumeStateIds(): List<Int> {
|
||||||
|
val folder = "$currentAccount/$RESULT_RESUME_WATCHING"
|
||||||
|
return getKeys(folder).mapNotNull {
|
||||||
|
it.removePrefix("$folder/").toIntOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.setLastWatched(
|
||||||
|
parentId: Int?,
|
||||||
|
episodeId: Int?,
|
||||||
|
episode: Int?,
|
||||||
|
season: Int?,
|
||||||
|
isFromDownload: Boolean = false
|
||||||
|
) {
|
||||||
|
if (parentId == null || episodeId == null) return
|
||||||
|
setKey(
|
||||||
|
"$currentAccount/$RESULT_RESUME_WATCHING",
|
||||||
|
parentId.toString(),
|
||||||
|
VideoDownloadHelper.ResumeWatching(
|
||||||
|
parentId,
|
||||||
|
episodeId,
|
||||||
|
episode,
|
||||||
|
season,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
isFromDownload
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.removeLastWatched(parentId: Int?) {
|
||||||
|
if (parentId == null) return
|
||||||
|
removeKey("$currentAccount/$RESULT_RESUME_WATCHING", parentId.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getLastWatched(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||||
|
if (id == null) return null
|
||||||
|
return getKey(
|
||||||
|
"$currentAccount/$RESULT_RESUME_WATCHING",
|
||||||
|
id.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.setBookmarkedData(id: Int?, data: BookmarkedData) {
|
fun Context.setBookmarkedData(id: Int?, data: BookmarkedData) {
|
||||||
if (id == null) return
|
if (id == null) return
|
||||||
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data)
|
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
|
|
||||||
|
@ -25,4 +26,13 @@ object VideoDownloadHelper {
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val cacheTime: Long,
|
val cacheTime: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ResumeWatching(
|
||||||
|
val parentId: Int,
|
||||||
|
val episodeId: Int,
|
||||||
|
val episode: Int?,
|
||||||
|
val season: Int?,
|
||||||
|
val updateTime : Long,
|
||||||
|
val isFromDownload: Boolean,
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -9,14 +9,30 @@
|
||||||
android:id="@+id/home_root"
|
android:id="@+id/home_root"
|
||||||
tools:context=".ui.home.HomeFragment">
|
tools:context=".ui.home.HomeFragment">
|
||||||
|
|
||||||
<ProgressBar
|
<FrameLayout
|
||||||
android:visibility="visible"
|
|
||||||
tools:visibility="gone"
|
|
||||||
android:id="@+id/home_loading"
|
android:id="@+id/home_loading"
|
||||||
android:layout_gravity="center"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="50dp"
|
android:layout_height="match_parent">
|
||||||
android:layout_height="50dp">
|
<ProgressBar
|
||||||
</ProgressBar>
|
android:layout_gravity="center"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp">
|
||||||
|
</ProgressBar>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/home_change_api_loading"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
|
||||||
|
android:src="@drawable/ic_outline_settings_24"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:contentDescription="@string/home_change_provider">
|
||||||
|
</ImageView>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
tools:visibility="gone"
|
tools:visibility="gone"
|
||||||
android:id="@+id/home_loading_error"
|
android:id="@+id/home_loading_error"
|
||||||
|
@ -33,8 +49,7 @@
|
||||||
android:id="@+id/home_reload_connectionerror"
|
android:id="@+id/home_reload_connectionerror"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:minWidth="200dp"
|
android:minWidth="200dp"
|
||||||
>
|
/>
|
||||||
</com.google.android.material.button.MaterialButton>
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
style="@style/BlackButton"
|
style="@style/BlackButton"
|
||||||
|
@ -44,8 +59,7 @@
|
||||||
android:id="@+id/home_reload_connection_open_in_browser"
|
android:id="@+id/home_reload_connection_open_in_browser"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:minWidth="200dp"
|
android:minWidth="200dp"
|
||||||
>
|
/>
|
||||||
</com.google.android.material.button.MaterialButton>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
@ -53,8 +67,8 @@
|
||||||
android:id="@+id/result_error_text"
|
android:id="@+id/result_error_text"
|
||||||
android:textColor="?attr/textColor"
|
android:textColor="?attr/textColor"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
</TextView>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/home_loaded"
|
android:id="@+id/home_loaded"
|
||||||
|
@ -156,6 +170,51 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_watch_holder"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:id="@+id/home_watch_child_more_info"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:id="@+id/home_watch_parent_item_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?attr/textColor"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/continue_watching"
|
||||||
|
/>
|
||||||
|
<ImageView
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
android:src="@drawable/ic_baseline_arrow_forward_24"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/home_more_info">
|
||||||
|
</ImageView>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
android:id="@+id/home_watch_child_recyclerview"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/home_result_grid"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_bookmarked_holder"
|
android:id="@+id/home_bookmarked_holder"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|
|
@ -46,22 +46,26 @@
|
||||||
android:paddingEnd="5dp"
|
android:paddingEnd="5dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
/>
|
/>
|
||||||
<TextView
|
<ImageView
|
||||||
android:text="Movie"
|
android:id="@+id/search_item_download_play"
|
||||||
android:visibility="gone"
|
android:layout_gravity="center"
|
||||||
android:id="@+id/text_type"
|
android:src="@drawable/play_button"
|
||||||
android:textColor="@color/textColor"
|
android:layout_width="60dp"
|
||||||
android:paddingRight="10dp"
|
android:layout_height="60dp">
|
||||||
android:paddingLeft="10dp"
|
</ImageView>
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:layout_marginBottom="5dp"
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
android:layout_gravity="start"
|
android:layout_marginBottom="-1.5dp"
|
||||||
android:paddingBottom="8dp"
|
android:id="@+id/watchProgress"
|
||||||
android:minWidth="50dp"
|
android:progressTint="@color/colorPrimary"
|
||||||
android:gravity="center"
|
android:progressBackgroundTint="@color/colorPrimary"
|
||||||
android:background="@drawable/type_bg_color"
|
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||||
android:layout_width="wrap_content" android:layout_height="wrap_content">
|
android:layout_width="match_parent"
|
||||||
</TextView>
|
tools:progress="50"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_height="5dp">
|
||||||
|
</androidx.core.widget.ContentLoadingProgressBar>
|
||||||
|
|
||||||
<!--<View
|
<!--<View
|
||||||
android:id="@+id/search_result_lang"
|
android:id="@+id/search_result_lang"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
|
|
|
@ -91,4 +91,8 @@
|
||||||
<string name="subs_auto_select_language">Auto Select Language</string>
|
<string name="subs_auto_select_language">Auto Select Language</string>
|
||||||
<string name="subs_download_languages">Download Languages</string>
|
<string name="subs_download_languages">Download Languages</string>
|
||||||
<string name="subs_hold_to_reset_to_default">Hold to reset to default</string>
|
<string name="subs_hold_to_reset_to_default">Hold to reset to default</string>
|
||||||
|
<string name="continue_watching">Continue Watching</string>
|
||||||
|
|
||||||
|
<string name="action_remove_watching">Remove</string>
|
||||||
|
<string name="action_open_watching">More Info</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue