2021-05-16 18:28:00 +00:00
|
|
|
package com.lagradost.cloudstream3.ui.result
|
|
|
|
|
2021-05-18 13:43:32 +00:00
|
|
|
import android.annotation.SuppressLint
|
2022-08-22 21:30:53 +00:00
|
|
|
import android.content.Context
|
2021-09-03 09:13:34 +00:00
|
|
|
import android.content.Intent
|
2021-07-17 15:56:26 +00:00
|
|
|
import android.content.Intent.*
|
2021-12-10 19:48:21 +00:00
|
|
|
import android.content.res.ColorStateList
|
2021-06-10 15:15:14 +00:00
|
|
|
import android.net.Uri
|
2022-04-02 01:38:55 +00:00
|
|
|
import android.os.Build
|
2021-05-16 18:28:00 +00:00
|
|
|
import android.os.Bundle
|
2022-04-02 01:38:55 +00:00
|
|
|
import android.text.Editable
|
2021-05-16 18:28:00 +00:00
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
2022-08-18 00:54:05 +00:00
|
|
|
import android.widget.AbsListView
|
|
|
|
import android.widget.ArrayAdapter
|
|
|
|
import android.widget.ImageView
|
2023-02-19 18:27:40 +00:00
|
|
|
import android.widget.Toast
|
2021-05-18 13:43:32 +00:00
|
|
|
import androidx.appcompat.app.AlertDialog
|
2021-12-10 00:20:24 +00:00
|
|
|
import androidx.core.view.isGone
|
2021-09-19 22:36:32 +00:00
|
|
|
import androidx.core.view.isVisible
|
2022-04-02 01:38:55 +00:00
|
|
|
import androidx.core.widget.doOnTextChanged
|
2022-01-07 19:27:25 +00:00
|
|
|
import androidx.lifecycle.ViewModelProvider
|
2021-09-19 22:36:32 +00:00
|
|
|
import androidx.preference.PreferenceManager
|
2023-03-18 22:55:58 +00:00
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2022-02-04 20:49:35 +00:00
|
|
|
import com.discord.panels.OverlappingPanelsLayout
|
2022-10-28 16:43:14 +00:00
|
|
|
import com.google.android.material.chip.Chip
|
2022-11-01 17:01:29 +00:00
|
|
|
import com.google.android.material.chip.ChipDrawable
|
2022-08-04 01:19:59 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
2022-08-18 00:54:05 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
2022-06-17 20:43:42 +00:00
|
|
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
2023-02-19 18:27:40 +00:00
|
|
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
2022-08-04 22:26:33 +00:00
|
|
|
import com.lagradost.cloudstream3.DubStatus
|
2022-08-22 21:30:53 +00:00
|
|
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
2022-08-04 22:26:33 +00:00
|
|
|
import com.lagradost.cloudstream3.R
|
|
|
|
import com.lagradost.cloudstream3.SearchResponse
|
|
|
|
import com.lagradost.cloudstream3.TvType
|
2022-08-03 00:04:03 +00:00
|
|
|
import com.lagradost.cloudstream3.mvvm.*
|
2023-02-19 18:27:40 +00:00
|
|
|
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
|
2022-06-18 23:03:25 +00:00
|
|
|
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
2021-06-15 23:25:58 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.WatchType
|
2022-08-03 17:57:38 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
2021-07-24 20:50:57 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
2021-11-20 00:41:37 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
2022-10-05 22:14:42 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
|
2022-03-29 21:50:07 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
2021-12-10 19:48:21 +00:00
|
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
2022-08-03 17:27:49 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.*
|
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
2022-06-29 01:20:23 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
2022-01-07 19:27:25 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
2021-12-16 23:45:20 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
2022-08-03 17:27:49 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
2023-01-20 22:26:46 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState
|
2021-06-15 16:07:20 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
2021-12-10 19:48:21 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
2021-08-24 22:19:15 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
2022-08-03 00:04:03 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
2021-08-24 22:19:15 +00:00
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
|
|
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.download_button
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_cast_items
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_cast_text
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_coming_soon
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_data_holder
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_description
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_dub_select
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_episode_loading
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_episode_select
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_episodes
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_error_text
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_finish_loading
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_info
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_loading
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_loading_error
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_meta_duration
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_meta_rating
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_meta_site
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_meta_type
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_meta_year
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_next_airing
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_next_airing_time
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_no_episodes
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_play_movie
|
2022-12-23 21:53:51 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_poster
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_poster_background
|
2022-12-23 21:53:51 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_poster_holder
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_reload_connection_open_in_browser
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_reload_connectionerror
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_parent
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_progress_holder
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_series_button
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_series_progress
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_series_progress_text
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_resume_series_title
|
2023-07-15 21:43:09 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_season_button
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_tag
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_tag_holder
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_title
|
|
|
|
import kotlinx.android.synthetic.main.fragment_result.result_vpn
|
2022-02-04 20:49:35 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
2022-08-04 22:26:33 +00:00
|
|
|
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
2022-04-01 20:05:34 +00:00
|
|
|
import kotlinx.android.synthetic.main.result_sync.*
|
2022-11-01 17:01:29 +00:00
|
|
|
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
2022-09-22 17:38:45 +00:00
|
|
|
import kotlinx.coroutines.delay
|
2022-08-03 00:04:03 +00:00
|
|
|
import kotlinx.coroutines.runBlocking
|
2022-08-06 18:36:45 +00:00
|
|
|
|
2021-07-29 15:16:08 +00:00
|
|
|
const val START_ACTION_RESUME_LATEST = 1
|
2021-08-25 15:28:25 +00:00
|
|
|
const val START_ACTION_LOAD_EP = 2
|
|
|
|
|
2023-01-20 22:26:46 +00:00
|
|
|
/**
|
|
|
|
* Future proofed way to mark episodes as watched
|
|
|
|
**/
|
|
|
|
enum class VideoWatchState {
|
|
|
|
/** Default value when no key is set */
|
|
|
|
None,
|
|
|
|
Watched
|
|
|
|
}
|
|
|
|
|
2021-05-18 13:43:32 +00:00
|
|
|
data class ResultEpisode(
|
2022-01-07 19:27:25 +00:00
|
|
|
val headerName: String,
|
2021-05-18 13:43:32 +00:00
|
|
|
val name: String?,
|
2021-06-10 15:15:14 +00:00
|
|
|
val poster: String?,
|
2021-05-18 13:43:32 +00:00
|
|
|
val episode: Int,
|
2022-08-01 01:00:48 +00:00
|
|
|
val seasonIndex: Int?, // this is the "season" index used season names
|
|
|
|
val season: Int?, // this is the display
|
2021-06-14 16:58:43 +00:00
|
|
|
val data: String,
|
2021-05-18 13:43:32 +00:00
|
|
|
val apiName: String,
|
|
|
|
val id: Int,
|
2021-05-22 22:25:56 +00:00
|
|
|
val index: Int,
|
2021-06-15 16:07:20 +00:00
|
|
|
val position: Long, // time in MS
|
2021-06-10 15:15:14 +00:00
|
|
|
val duration: Long, // duration in MS
|
2021-06-26 19:32:50 +00:00
|
|
|
val rating: Int?,
|
2021-12-10 00:20:24 +00:00
|
|
|
val description: String?,
|
2021-09-19 22:36:32 +00:00
|
|
|
val isFiller: Boolean?,
|
2022-01-07 19:27:25 +00:00
|
|
|
val tvType: TvType,
|
2022-02-13 14:51:55 +00:00
|
|
|
val parentId: Int,
|
2023-01-20 22:26:46 +00:00
|
|
|
/**
|
|
|
|
* Conveys if the episode itself is marked as watched
|
|
|
|
**/
|
|
|
|
val videoWatchState: VideoWatchState
|
2021-05-18 13:43:32 +00:00
|
|
|
)
|
2021-05-16 18:28:00 +00:00
|
|
|
|
2021-06-15 16:07:20 +00:00
|
|
|
fun ResultEpisode.getRealPosition(): Long {
|
|
|
|
if (duration <= 0) return 0
|
|
|
|
val percentage = position * 100 / duration
|
|
|
|
if (percentage <= 5 || percentage >= 95) return 0
|
|
|
|
return position
|
|
|
|
}
|
|
|
|
|
2021-06-16 17:40:02 +00:00
|
|
|
fun ResultEpisode.getDisplayPosition(): Long {
|
|
|
|
if (duration <= 0) return 0
|
|
|
|
val percentage = position * 100 / duration
|
|
|
|
if (percentage <= 1) return 0
|
|
|
|
if (percentage <= 5) return 5 * duration / 100
|
|
|
|
if (percentage >= 95) return duration
|
|
|
|
return position
|
|
|
|
}
|
|
|
|
|
2021-12-16 23:45:20 +00:00
|
|
|
fun buildResultEpisode(
|
2022-01-07 19:27:25 +00:00
|
|
|
headerName: String,
|
2022-04-26 13:52:30 +00:00
|
|
|
name: String? = null,
|
|
|
|
poster: String? = null,
|
2021-06-15 16:07:20 +00:00
|
|
|
episode: Int,
|
2022-08-01 01:00:48 +00:00
|
|
|
seasonIndex: Int? = null,
|
2022-04-26 13:52:30 +00:00
|
|
|
season: Int? = null,
|
2021-06-15 16:07:20 +00:00
|
|
|
data: String,
|
|
|
|
apiName: String,
|
|
|
|
id: Int,
|
|
|
|
index: Int,
|
2022-04-26 13:52:30 +00:00
|
|
|
rating: Int? = null,
|
|
|
|
description: String? = null,
|
|
|
|
isFiller: Boolean? = null,
|
2022-01-07 19:27:25 +00:00
|
|
|
tvType: TvType,
|
2022-02-13 14:51:55 +00:00
|
|
|
parentId: Int,
|
2021-06-15 16:07:20 +00:00
|
|
|
): ResultEpisode {
|
|
|
|
val posDur = getViewPos(id)
|
2023-01-20 22:26:46 +00:00
|
|
|
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
|
2021-06-26 19:32:50 +00:00
|
|
|
return ResultEpisode(
|
2022-01-07 19:27:25 +00:00
|
|
|
headerName,
|
2021-06-26 19:32:50 +00:00
|
|
|
name,
|
2021-06-15 16:07:20 +00:00
|
|
|
poster,
|
|
|
|
episode,
|
2022-08-01 01:00:48 +00:00
|
|
|
seasonIndex,
|
2021-06-15 16:07:20 +00:00
|
|
|
season,
|
|
|
|
data,
|
|
|
|
apiName,
|
|
|
|
id,
|
|
|
|
index,
|
|
|
|
posDur?.position ?: 0,
|
2021-06-26 19:32:50 +00:00
|
|
|
posDur?.duration ?: 0,
|
|
|
|
rating,
|
2021-11-30 17:59:52 +00:00
|
|
|
description,
|
2022-01-07 19:27:25 +00:00
|
|
|
isFiller,
|
|
|
|
tvType,
|
|
|
|
parentId,
|
2023-01-20 22:26:46 +00:00
|
|
|
videoWatchState
|
2021-06-26 19:32:50 +00:00
|
|
|
)
|
2021-06-15 16:07:20 +00:00
|
|
|
}
|
|
|
|
|
2021-07-29 15:16:08 +00:00
|
|
|
/** 0f-1f */
|
2021-06-10 15:15:14 +00:00
|
|
|
fun ResultEpisode.getWatchProgress(): Float {
|
2021-07-29 15:16:08 +00:00
|
|
|
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
|
2021-06-10 15:15:14 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 22:26:33 +00:00
|
|
|
open class ResultFragment : ResultTrailerPlayer() {
|
2021-05-22 22:25:56 +00:00
|
|
|
companion object {
|
2022-05-02 21:32:28 +00:00
|
|
|
const val URL_BUNDLE = "url"
|
|
|
|
const val API_NAME_BUNDLE = "apiName"
|
|
|
|
const val SEASON_BUNDLE = "season"
|
|
|
|
const val EPISODE_BUNDLE = "episode"
|
|
|
|
const val START_ACTION_BUNDLE = "startAction"
|
|
|
|
const val START_VALUE_BUNDLE = "startValue"
|
|
|
|
const val RESTART_BUNDLE = "restart"
|
2022-08-04 22:26:33 +00:00
|
|
|
|
2022-05-02 21:32:28 +00:00
|
|
|
fun newInstance(
|
|
|
|
card: SearchResponse, startAction: Int = 0, startValue: Int? = null
|
|
|
|
): Bundle {
|
|
|
|
return Bundle().apply {
|
|
|
|
putString(URL_BUNDLE, card.url)
|
|
|
|
putString(API_NAME_BUNDLE, card.apiName)
|
|
|
|
if (card is DataStoreHelper.ResumeWatchingResult) {
|
|
|
|
if (card.season != null)
|
|
|
|
putInt(SEASON_BUNDLE, card.season)
|
|
|
|
if (card.episode != null)
|
|
|
|
putInt(EPISODE_BUNDLE, card.episode)
|
|
|
|
}
|
|
|
|
putInt(START_ACTION_BUNDLE, startAction)
|
|
|
|
if (startValue != null)
|
|
|
|
putInt(START_VALUE_BUNDLE, startValue)
|
2022-08-04 01:19:59 +00:00
|
|
|
|
|
|
|
|
2022-05-02 21:32:28 +00:00
|
|
|
putBoolean(RESTART_BUNDLE, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 12:42:25 +00:00
|
|
|
fun newInstance(
|
|
|
|
url: String,
|
|
|
|
apiName: String,
|
|
|
|
startAction: Int = 0,
|
|
|
|
startValue: Int = 0
|
|
|
|
): Bundle {
|
2021-09-20 21:11:36 +00:00
|
|
|
return Bundle().apply {
|
2022-05-02 21:32:28 +00:00
|
|
|
putString(URL_BUNDLE, url)
|
|
|
|
putString(API_NAME_BUNDLE, apiName)
|
|
|
|
putInt(START_ACTION_BUNDLE, startAction)
|
|
|
|
putInt(START_VALUE_BUNDLE, startValue)
|
|
|
|
putBoolean(RESTART_BUNDLE, true)
|
2021-05-18 13:43:32 +00:00
|
|
|
}
|
2021-09-20 21:11:36 +00:00
|
|
|
}
|
2022-01-18 14:10:01 +00:00
|
|
|
|
|
|
|
fun updateUI() {
|
|
|
|
updateUIListener?.invoke()
|
|
|
|
}
|
|
|
|
|
2022-01-31 20:47:59 +00:00
|
|
|
private var updateUIListener: (() -> Unit)? = null
|
2021-05-22 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2022-08-06 16:08:20 +00:00
|
|
|
open fun setTrailers(trailers: List<ExtractorLink>?) {}
|
2022-08-04 22:26:33 +00:00
|
|
|
|
|
|
|
protected lateinit var viewModel: ResultViewModel2 //by activityViewModels()
|
|
|
|
protected lateinit var syncModel: SyncViewModel
|
|
|
|
protected open val resultLayout = R.layout.fragment_result_swipe
|
2021-05-16 18:28:00 +00:00
|
|
|
|
|
|
|
override fun onCreateView(
|
|
|
|
inflater: LayoutInflater,
|
|
|
|
container: ViewGroup?,
|
2021-05-18 13:43:32 +00:00
|
|
|
savedInstanceState: Bundle?,
|
2021-05-16 18:28:00 +00:00
|
|
|
): View? {
|
2022-01-07 19:27:25 +00:00
|
|
|
viewModel =
|
2022-08-01 01:00:48 +00:00
|
|
|
ViewModelProvider(this)[ResultViewModel2::class.java]
|
2022-04-01 20:05:34 +00:00
|
|
|
syncModel =
|
|
|
|
ViewModelProvider(this)[SyncViewModel::class.java]
|
|
|
|
|
2022-08-04 22:26:33 +00:00
|
|
|
return inflater.inflate(resultLayout, container, false)
|
2021-05-16 18:28:00 +00:00
|
|
|
}
|
|
|
|
|
2021-07-28 19:14:45 +00:00
|
|
|
override fun onDestroyView() {
|
2022-07-17 00:41:01 +00:00
|
|
|
updateUIListener = null
|
2021-07-28 19:14:45 +00:00
|
|
|
super.onDestroyView()
|
|
|
|
}
|
|
|
|
|
2021-06-16 00:15:07 +00:00
|
|
|
override fun onResume() {
|
2022-08-22 21:30:53 +00:00
|
|
|
afterPluginsLoadedEvent += ::reloadViewModel
|
2021-06-16 00:15:07 +00:00
|
|
|
super.onResume()
|
|
|
|
activity?.let {
|
|
|
|
it.window?.navigationBarColor =
|
2021-09-19 20:33:39 +00:00
|
|
|
it.colorFromAttribute(R.attr.primaryBlackBackground)
|
2021-06-16 00:15:07 +00:00
|
|
|
}
|
2021-05-22 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
override fun onDestroy() {
|
|
|
|
afterPluginsLoadedEvent -= ::reloadViewModel
|
|
|
|
super.onDestroy()
|
|
|
|
}
|
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
/// 0 = LOADING, 1 = ERROR LOADING, 2 = LOADED
|
2021-07-02 18:46:18 +00:00
|
|
|
private fun updateVisStatus(state: Int) {
|
2021-06-16 16:54:07 +00:00
|
|
|
when (state) {
|
|
|
|
0 -> {
|
2021-12-10 19:48:21 +00:00
|
|
|
result_bookmark_fab?.isGone = true
|
|
|
|
result_loading?.isVisible = true
|
|
|
|
result_finish_loading?.isVisible = false
|
|
|
|
result_loading_error?.isVisible = false
|
2021-06-16 16:54:07 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
1 -> {
|
2021-12-10 19:48:21 +00:00
|
|
|
result_bookmark_fab?.isGone = true
|
|
|
|
result_loading?.isVisible = false
|
|
|
|
result_finish_loading?.isVisible = false
|
|
|
|
result_loading_error?.isVisible = true
|
2022-08-02 00:43:42 +00:00
|
|
|
result_reload_connection_open_in_browser?.isVisible = true
|
2021-06-16 16:54:07 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
2 -> {
|
2022-09-22 19:04:55 +00:00
|
|
|
result_bookmark_fab?.isGone = isTrueTvSettings()
|
2021-12-10 19:48:21 +00:00
|
|
|
result_bookmark_fab?.extend()
|
2022-08-20 01:06:35 +00:00
|
|
|
//if (result_bookmark_button?.context?.isTrueTvSettings() == true) {
|
|
|
|
// when {
|
|
|
|
// result_play_movie?.isVisible == true -> {
|
|
|
|
// result_play_movie?.requestFocus()
|
|
|
|
// }
|
|
|
|
// result_resume_series_button?.isVisible == true -> {
|
|
|
|
// result_resume_series_button?.requestFocus()
|
|
|
|
// }
|
|
|
|
// else -> {
|
|
|
|
// result_bookmark_button?.requestFocus()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
2021-10-09 21:59:37 +00:00
|
|
|
|
2021-12-10 19:48:21 +00:00
|
|
|
result_loading?.isVisible = false
|
|
|
|
result_finish_loading?.isVisible = true
|
|
|
|
result_loading_error?.isVisible = false
|
2021-06-16 16:54:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-04 22:26:33 +00:00
|
|
|
open fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
2021-12-13 18:41:33 +00:00
|
|
|
|
|
|
|
}
|
2021-07-29 15:16:08 +00:00
|
|
|
|
2022-01-18 14:10:01 +00:00
|
|
|
private fun updateUI() {
|
2022-04-08 19:38:19 +00:00
|
|
|
syncModel.updateUserData()
|
2022-01-18 14:10:01 +00:00
|
|
|
viewModel.reloadEpisodes()
|
|
|
|
}
|
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
open fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
|
2022-08-05 23:41:35 +00:00
|
|
|
when (data) {
|
2023-07-13 21:18:37 +00:00
|
|
|
is Resource.Success -> {
|
2022-08-05 23:41:35 +00:00
|
|
|
data.value.let { (text, ep) ->
|
|
|
|
result_play_movie.setText(text)
|
|
|
|
result_play_movie?.setOnClickListener {
|
|
|
|
viewModel.handleAction(
|
|
|
|
activity,
|
|
|
|
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
result_play_movie?.setOnLongClickListener {
|
|
|
|
viewModel.handleAction(
|
|
|
|
activity,
|
|
|
|
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
|
|
|
|
)
|
|
|
|
return@setOnLongClickListener true
|
|
|
|
}
|
|
|
|
|
2023-07-15 21:43:09 +00:00
|
|
|
val show = viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
|
|
|
|
if(show) {
|
|
|
|
download_button?.setDefaultClickListener(
|
2022-08-05 23:41:35 +00:00
|
|
|
VideoDownloadHelper.DownloadEpisodeCached(
|
|
|
|
ep.name,
|
|
|
|
ep.poster,
|
|
|
|
0,
|
|
|
|
null,
|
|
|
|
ep.id,
|
|
|
|
ep.id,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
System.currentTimeMillis(),
|
|
|
|
)
|
|
|
|
) { click ->
|
2023-07-15 21:43:09 +00:00
|
|
|
println("Click:$click")
|
2022-08-06 16:08:20 +00:00
|
|
|
when (click.action) {
|
2022-08-05 23:41:35 +00:00
|
|
|
DOWNLOAD_ACTION_DOWNLOAD -> {
|
|
|
|
viewModel.handleAction(
|
|
|
|
activity,
|
|
|
|
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
|
|
|
|
)
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-08-05 23:41:35 +00:00
|
|
|
else -> handleDownloadClick(activity, click)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-15 21:43:09 +00:00
|
|
|
download_button?.isVisible = show
|
|
|
|
|
2022-08-05 23:41:35 +00:00
|
|
|
}
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-08-05 23:41:35 +00:00
|
|
|
else -> {
|
2023-07-15 21:43:09 +00:00
|
|
|
download_button?.isVisible = false
|
2022-08-05 23:41:35 +00:00
|
|
|
result_play_movie?.isVisible = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
open fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
|
2022-08-05 23:41:35 +00:00
|
|
|
when (episodes) {
|
2023-07-13 21:18:37 +00:00
|
|
|
is Resource.Loading -> {
|
2022-08-05 23:41:35 +00:00
|
|
|
result_episode_loading?.isVisible = true
|
|
|
|
result_episodes?.isVisible = false
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
|
|
|
is Resource.Success -> {
|
2022-08-05 23:41:35 +00:00
|
|
|
result_episodes?.isVisible = true
|
|
|
|
result_episode_loading?.isVisible = false
|
2022-09-22 17:38:45 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Okay so what is this fuckery?
|
|
|
|
* Basically Android TV will crash if you request a new focus while
|
|
|
|
* the adapter gets updated.
|
|
|
|
*
|
|
|
|
* This means that if you load thumbnails and request a next focus at the same time
|
|
|
|
* the app will crash without any way to catch it!
|
|
|
|
*
|
|
|
|
* How to bypass this?
|
|
|
|
* This code basically steals the focus for 500ms and puts it in an inescapable view
|
|
|
|
* then lets out the focus by requesting focus to result_episodes
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Do not use this.isTv, that is the player
|
|
|
|
val isTv = isTvSettings()
|
|
|
|
val hasEpisodes =
|
|
|
|
!(result_episodes?.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
|
|
|
|
|
|
|
|
if (isTv && hasEpisodes) {
|
|
|
|
// Make it impossible to focus anywhere else!
|
|
|
|
temporary_no_focus?.isFocusable = true
|
|
|
|
temporary_no_focus?.requestFocus()
|
|
|
|
}
|
|
|
|
|
2023-01-28 22:38:02 +00:00
|
|
|
(result_episodes?.adapter as? EpisodeAdapter)?.updateList(episodes.value)
|
2022-09-22 17:38:45 +00:00
|
|
|
|
|
|
|
if (isTv && hasEpisodes) main {
|
|
|
|
delay(500)
|
|
|
|
temporary_no_focus?.isFocusable = false
|
|
|
|
// This might make some people sad as it changes the focus when leaving an episode :(
|
|
|
|
result_episodes?.requestFocus()
|
|
|
|
}
|
2022-08-05 23:41:35 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
|
|
|
else -> {
|
|
|
|
result_episode_loading?.isVisible = false
|
|
|
|
result_episodes?.isVisible = false
|
|
|
|
}
|
2022-08-05 23:41:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
data class StoredData(
|
|
|
|
val url: String?,
|
|
|
|
val apiName: String,
|
|
|
|
val showFillers: Boolean,
|
|
|
|
val dubStatus: DubStatus,
|
2022-10-05 22:14:42 +00:00
|
|
|
val start: AutoResume?,
|
|
|
|
val playerAction: Int
|
2022-08-22 21:30:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
private fun getStoredData(context: Context): StoredData? {
|
|
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
|
|
|
val url = arguments?.getString(URL_BUNDLE)
|
|
|
|
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return null
|
|
|
|
val showFillers =
|
|
|
|
settingsManager.getBoolean(context.getString(R.string.show_fillers_key), false)
|
|
|
|
val dubStatus = if (context.getApiDubstatusSettings()
|
|
|
|
.contains(DubStatus.Dubbed)
|
|
|
|
) DubStatus.Dubbed else DubStatus.Subbed
|
|
|
|
val startAction = arguments?.getInt(START_ACTION_BUNDLE)
|
|
|
|
|
2022-10-05 22:14:42 +00:00
|
|
|
val playerAction = getPlayerAction(context)
|
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
val start = startAction?.let { action ->
|
|
|
|
val startValue = arguments?.getInt(START_VALUE_BUNDLE)
|
|
|
|
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
|
|
|
|
val resumeSeason = arguments?.getInt(SEASON_BUNDLE)
|
|
|
|
|
|
|
|
arguments?.remove(START_VALUE_BUNDLE)
|
|
|
|
arguments?.remove(START_ACTION_BUNDLE)
|
|
|
|
AutoResume(
|
|
|
|
startAction = action,
|
|
|
|
id = startValue,
|
|
|
|
episode = resumeEpisode,
|
|
|
|
season = resumeSeason
|
|
|
|
)
|
|
|
|
}
|
2022-10-05 22:14:42 +00:00
|
|
|
return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
|
2022-08-22 21:30:53 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 22:28:31 +00:00
|
|
|
private fun reloadViewModel(forceReload: Boolean) {
|
|
|
|
if (!viewModel.hasLoaded() || forceReload) {
|
2022-08-22 21:30:53 +00:00
|
|
|
val storedData = getStoredData(activity ?: context ?: return) ?: return
|
|
|
|
|
2022-09-22 17:38:45 +00:00
|
|
|
viewModel.load(
|
|
|
|
activity,
|
|
|
|
storedData.url ?: return,
|
|
|
|
storedData.apiName,
|
|
|
|
storedData.showFillers,
|
|
|
|
storedData.dubStatus,
|
|
|
|
storedData.start
|
|
|
|
)
|
2022-08-22 21:30:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-18 13:43:32 +00:00
|
|
|
@SuppressLint("SetTextI18n")
|
2021-05-16 18:28:00 +00:00
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
2022-07-17 00:41:01 +00:00
|
|
|
|
2023-03-18 22:55:58 +00:00
|
|
|
result_cast_items?.layoutManager = object : LinearListLayout(view.context) {
|
|
|
|
override fun onRequestChildFocus(
|
|
|
|
parent: RecyclerView,
|
|
|
|
state: RecyclerView.State,
|
|
|
|
child: View,
|
|
|
|
focused: View?
|
|
|
|
): Boolean {
|
|
|
|
// Make the cast always focus the first visible item when focused
|
|
|
|
// from somewhere else. Otherwise it jumps to the last item.
|
|
|
|
return if (parent.focusedChild == null) {
|
|
|
|
scrollToPosition(this.findFirstCompletelyVisibleItemPosition())
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
super.onRequestChildFocus(parent, state, child, focused)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.apply {
|
|
|
|
this.orientation = RecyclerView.HORIZONTAL
|
|
|
|
}
|
2022-07-24 02:49:15 +00:00
|
|
|
result_cast_items?.adapter = ActorAdaptor()
|
2022-06-16 23:58:55 +00:00
|
|
|
|
2022-01-18 14:10:01 +00:00
|
|
|
updateUIListener = ::updateUI
|
|
|
|
|
2022-05-02 21:32:28 +00:00
|
|
|
val restart = arguments?.getBoolean(RESTART_BUNDLE) ?: false
|
2021-09-20 21:11:36 +00:00
|
|
|
if (restart) {
|
2022-05-02 21:32:28 +00:00
|
|
|
arguments?.putBoolean(RESTART_BUNDLE, false)
|
2021-09-20 21:11:36 +00:00
|
|
|
}
|
|
|
|
|
2021-07-28 01:04:32 +00:00
|
|
|
activity?.window?.decorView?.clearFocus()
|
|
|
|
hideKeyboard()
|
2022-06-17 20:43:42 +00:00
|
|
|
context?.updateHasTrailers()
|
2022-01-07 19:27:25 +00:00
|
|
|
activity?.loadCache()
|
2021-07-28 01:04:32 +00:00
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
fixPaddingStatusbar(result_top_bar)
|
2021-09-19 20:33:39 +00:00
|
|
|
//activity?.fixPaddingStatusbar(result_barstatus)
|
2021-06-06 18:06:01 +00:00
|
|
|
|
2021-12-10 00:20:24 +00:00
|
|
|
/* val backParameter = result_back.layoutParams as FrameLayout.LayoutParams
|
|
|
|
backParameter.setMargins(
|
|
|
|
backParameter.leftMargin,
|
|
|
|
backParameter.topMargin + requireContext().getStatusBarHeight(),
|
|
|
|
backParameter.rightMargin,
|
|
|
|
backParameter.bottomMargin
|
|
|
|
)
|
|
|
|
result_back.layoutParams = backParameter*/
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2021-05-20 15:22:28 +00:00
|
|
|
// activity?.fixPaddingStatusbar(result_toolbar)
|
2021-05-18 13:43:32 +00:00
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
val storedData = (activity ?: context)?.let {
|
|
|
|
getStoredData(it)
|
2022-08-04 01:19:59 +00:00
|
|
|
}
|
2022-08-22 21:30:53 +00:00
|
|
|
syncModel.addFromUrl(storedData?.url)
|
2021-09-20 21:11:36 +00:00
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
val api = getApiFromNameNull(storedData?.apiName)
|
2021-05-18 13:43:32 +00:00
|
|
|
|
2022-08-06 18:36:45 +00:00
|
|
|
result_episodes?.adapter =
|
2021-06-16 16:54:07 +00:00
|
|
|
EpisodeAdapter(
|
2023-07-13 21:18:37 +00:00
|
|
|
api?.hasDownloadSupport == true && !isTvSettings(),
|
2021-07-24 20:50:57 +00:00
|
|
|
{ episodeClick ->
|
2022-08-02 00:43:42 +00:00
|
|
|
viewModel.handleAction(activity, episodeClick)
|
2021-07-24 20:50:57 +00:00
|
|
|
},
|
|
|
|
{ downloadClickEvent ->
|
2022-08-02 00:43:42 +00:00
|
|
|
handleDownloadClick(activity, downloadClickEvent)
|
2021-07-24 20:50:57 +00:00
|
|
|
}
|
|
|
|
)
|
2021-07-17 14:14:25 +00:00
|
|
|
|
2021-06-15 23:25:58 +00:00
|
|
|
|
2023-01-20 22:26:46 +00:00
|
|
|
observe(viewModel.episodeSynopsis) { description ->
|
|
|
|
view.context?.let { ctx ->
|
|
|
|
val builder: AlertDialog.Builder =
|
|
|
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
|
|
|
builder.setMessage(description.html())
|
|
|
|
.setTitle(R.string.synopsis)
|
|
|
|
.setOnDismissListener {
|
|
|
|
viewModel.releaseEpisodeSynopsis()
|
|
|
|
}
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 19:48:21 +00:00
|
|
|
observe(viewModel.watchStatus) { watchType ->
|
|
|
|
result_bookmark_button?.text = getString(watchType.stringRes)
|
|
|
|
result_bookmark_fab?.text = getString(watchType.stringRes)
|
|
|
|
|
2021-12-12 02:33:17 +00:00
|
|
|
if (watchType == WatchType.NONE) {
|
2021-12-10 19:48:21 +00:00
|
|
|
result_bookmark_fab?.context?.colorFromAttribute(R.attr.white)
|
|
|
|
} else {
|
|
|
|
result_bookmark_fab?.context?.colorFromAttribute(R.attr.colorPrimary)
|
|
|
|
}?.let {
|
|
|
|
val colorState = ColorStateList.valueOf(it)
|
|
|
|
result_bookmark_fab?.iconTint = colorState
|
|
|
|
result_bookmark_fab?.setTextColor(colorState)
|
|
|
|
}
|
|
|
|
|
|
|
|
result_bookmark_fab?.setOnClickListener { fab ->
|
2021-12-12 02:33:17 +00:00
|
|
|
activity?.showBottomDialog(
|
2021-12-10 19:48:21 +00:00
|
|
|
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
|
|
|
|
watchType.ordinal,
|
|
|
|
fab.context.getString(R.string.action_add_to_bookmarks),
|
2021-12-10 23:57:11 +00:00
|
|
|
showApply = false,
|
2021-12-10 19:48:21 +00:00
|
|
|
{}) {
|
2021-12-16 23:45:20 +00:00
|
|
|
viewModel.updateWatchStatus(WatchType.values()[it])
|
2021-12-10 19:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-20 01:06:35 +00:00
|
|
|
|
|
|
|
result_bookmark_button?.setOnClickListener { fab ->
|
|
|
|
activity?.showBottomDialog(
|
|
|
|
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
|
|
|
|
watchType.ordinal,
|
|
|
|
fab.context.getString(R.string.action_add_to_bookmarks),
|
|
|
|
showApply = false,
|
|
|
|
{}) {
|
|
|
|
viewModel.updateWatchStatus(WatchType.values()[it])
|
|
|
|
}
|
|
|
|
}
|
2021-06-15 23:25:58 +00:00
|
|
|
}
|
|
|
|
|
2022-03-27 16:45:02 +00:00
|
|
|
// This is to band-aid FireTV navigation
|
2022-08-28 23:52:15 +00:00
|
|
|
val isTv = isTvSettings()
|
2022-08-05 23:41:35 +00:00
|
|
|
result_season_button?.isFocusableInTouchMode = isTv
|
|
|
|
result_episode_select?.isFocusableInTouchMode = isTv
|
|
|
|
result_dub_select?.isFocusableInTouchMode = isTv
|
2022-03-27 16:45:02 +00:00
|
|
|
|
2022-04-02 01:38:55 +00:00
|
|
|
context?.let { ctx ->
|
|
|
|
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
|
|
|
/*
|
|
|
|
-1 -> None
|
|
|
|
0 -> Watching
|
|
|
|
1 -> Completed
|
|
|
|
2 -> OnHold
|
|
|
|
3 -> Dropped
|
|
|
|
4 -> PlanToWatch
|
|
|
|
5 -> ReWatching
|
|
|
|
*/
|
|
|
|
val items = listOf(
|
|
|
|
R.string.none,
|
|
|
|
R.string.type_watching,
|
|
|
|
R.string.type_completed,
|
|
|
|
R.string.type_on_hold,
|
|
|
|
R.string.type_dropped,
|
|
|
|
R.string.type_plan_to_watch,
|
|
|
|
R.string.type_re_watching
|
|
|
|
).map { ctx.getString(it) }
|
|
|
|
arrayAdapter.addAll(items)
|
|
|
|
result_sync_check?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
|
|
|
result_sync_check?.adapter = arrayAdapter
|
|
|
|
UIHelper.setListViewHeightBasedOnItems(result_sync_check)
|
|
|
|
|
|
|
|
result_sync_check?.setOnItemClickListener { _, _, which, _ ->
|
|
|
|
syncModel.setStatus(which - 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
result_sync_rating?.addOnChangeListener { _, value, _ ->
|
|
|
|
syncModel.setScore(value.toInt())
|
|
|
|
}
|
|
|
|
|
|
|
|
result_sync_add_episode?.setOnClickListener {
|
|
|
|
syncModel.setEpisodesDelta(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
result_sync_sub_episode?.setOnClickListener {
|
|
|
|
syncModel.setEpisodesDelta(-1)
|
|
|
|
}
|
|
|
|
|
2022-04-02 22:06:35 +00:00
|
|
|
result_sync_current_episodes?.doOnTextChanged { text, _, before, count ->
|
|
|
|
if (count == before) return@doOnTextChanged
|
2022-04-02 01:38:55 +00:00
|
|
|
text?.toString()?.toIntOrNull()?.let { ep ->
|
|
|
|
syncModel.setEpisodes(ep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-17 00:41:01 +00:00
|
|
|
|
2022-04-02 22:06:35 +00:00
|
|
|
observe(syncModel.synced) { list ->
|
|
|
|
result_sync_names?.text =
|
|
|
|
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
|
2022-04-03 01:13:02 +00:00
|
|
|
|
2022-04-22 18:53:58 +00:00
|
|
|
val newList = list.filter { it.isSynced && it.hasAccount }
|
2022-04-08 19:38:19 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
result_mini_sync?.isVisible = newList.isNotEmpty()
|
2023-01-28 22:38:02 +00:00
|
|
|
(result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
|
2022-04-02 22:06:35 +00:00
|
|
|
}
|
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
var currentSyncProgress = 0
|
2022-04-03 15:00:50 +00:00
|
|
|
|
|
|
|
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
|
|
|
|
result_sync_episodes?.max = (totalEpisodes ?: 0) * 1000
|
|
|
|
|
|
|
|
normalSafeApiCall {
|
|
|
|
val ctx = result_sync_max_episodes?.context
|
|
|
|
result_sync_max_episodes?.text =
|
|
|
|
totalEpisodes?.let { episodes ->
|
|
|
|
ctx?.getString(R.string.sync_total_episodes_some)?.format(episodes)
|
|
|
|
} ?: run {
|
|
|
|
ctx?.getString(R.string.sync_total_episodes_none)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-02 01:38:55 +00:00
|
|
|
observe(syncModel.metadata) { meta ->
|
|
|
|
when (meta) {
|
|
|
|
is Resource.Success -> {
|
|
|
|
val d = meta.value
|
2022-04-03 01:13:02 +00:00
|
|
|
result_sync_episodes?.progress = currentSyncProgress * 1000
|
2022-04-03 15:00:50 +00:00
|
|
|
setSyncMaxEpisodes(d.totalEpisodes)
|
2022-08-03 01:02:08 +00:00
|
|
|
|
2022-08-02 00:43:42 +00:00
|
|
|
viewModel.setMeta(d, syncModel.getSyncs())
|
2022-04-02 01:38:55 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-04-02 01:38:55 +00:00
|
|
|
is Resource.Loading -> {
|
|
|
|
result_sync_max_episodes?.text =
|
|
|
|
result_sync_max_episodes?.context?.getString(R.string.sync_total_episodes_none)
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-04-02 01:38:55 +00:00
|
|
|
else -> {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
observe(syncModel.userData) { status ->
|
2022-04-01 20:05:34 +00:00
|
|
|
var closed = false
|
|
|
|
when (status) {
|
|
|
|
is Resource.Failure -> {
|
|
|
|
result_sync_loading_shimmer?.stopShimmer()
|
|
|
|
result_sync_loading_shimmer?.isVisible = false
|
|
|
|
result_sync_holder?.isVisible = false
|
2022-04-03 21:41:28 +00:00
|
|
|
closed = true
|
2022-04-01 20:05:34 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-04-01 20:05:34 +00:00
|
|
|
is Resource.Loading -> {
|
|
|
|
result_sync_loading_shimmer?.startShimmer()
|
|
|
|
result_sync_loading_shimmer?.isVisible = true
|
|
|
|
result_sync_holder?.isVisible = false
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-04-01 20:05:34 +00:00
|
|
|
is Resource.Success -> {
|
|
|
|
result_sync_loading_shimmer?.stopShimmer()
|
|
|
|
result_sync_loading_shimmer?.isVisible = false
|
|
|
|
result_sync_holder?.isVisible = true
|
|
|
|
|
|
|
|
val d = status.value
|
|
|
|
result_sync_rating?.value = d.score?.toFloat() ?: 0.0f
|
2022-04-02 01:38:55 +00:00
|
|
|
result_sync_check?.setItemChecked(d.status + 1, true)
|
|
|
|
val watchedEpisodes = d.watchedEpisodes ?: 0
|
2022-04-03 01:13:02 +00:00
|
|
|
currentSyncProgress = watchedEpisodes
|
2022-04-03 15:00:50 +00:00
|
|
|
|
|
|
|
d.maxEpisodes?.let {
|
|
|
|
// don't directly call it because we don't want to override metadata observe
|
|
|
|
setSyncMaxEpisodes(it)
|
|
|
|
}
|
|
|
|
|
2022-04-02 01:38:55 +00:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
|
|
result_sync_episodes?.setProgress(watchedEpisodes * 1000, true)
|
|
|
|
} else {
|
|
|
|
result_sync_episodes?.progress = watchedEpisodes * 1000
|
|
|
|
}
|
|
|
|
result_sync_current_episodes?.text =
|
|
|
|
Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString())
|
|
|
|
normalSafeApiCall { // format might fail
|
|
|
|
context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)?.let {
|
|
|
|
result_sync_score_text?.text = it
|
|
|
|
}
|
|
|
|
}
|
2022-04-01 20:05:34 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2022-04-01 20:05:34 +00:00
|
|
|
null -> {
|
|
|
|
closed = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
|
|
|
|
}
|
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
observeNullable(viewModel.resumeWatching) { resume ->
|
|
|
|
if (resume == null) {
|
|
|
|
result_resume_parent?.isVisible = false
|
|
|
|
return@observeNullable
|
|
|
|
}
|
|
|
|
result_resume_parent?.isVisible = true
|
|
|
|
resume.progress?.let { progress ->
|
|
|
|
result_resume_series_title?.apply {
|
|
|
|
isVisible = !resume.isMovie
|
|
|
|
text =
|
|
|
|
if (resume.isMovie) null else activity?.getNameFull(
|
|
|
|
resume.result.name,
|
|
|
|
resume.result.episode,
|
|
|
|
resume.result.season
|
2022-05-14 17:07:34 +00:00
|
|
|
)
|
2021-08-25 15:28:25 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
result_resume_series_progress_text?.setText(progress.progressLeft)
|
|
|
|
result_resume_series_progress?.apply {
|
|
|
|
isVisible = true
|
|
|
|
this.max = progress.maxProgress
|
|
|
|
this.progress = progress.progress
|
2022-08-03 17:27:49 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
result_resume_progress_holder?.isVisible = true
|
|
|
|
} ?: run {
|
|
|
|
result_resume_progress_holder?.isVisible = false
|
|
|
|
result_resume_series_progress?.isVisible = false
|
|
|
|
result_resume_series_title?.isVisible = false
|
|
|
|
result_resume_series_progress_text?.isVisible = false
|
2021-07-29 15:16:08 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
|
|
|
result_resume_series_button?.isVisible = !resume.isMovie
|
|
|
|
result_resume_series_button_play?.isVisible = !resume.isMovie
|
|
|
|
|
|
|
|
val click = View.OnClickListener {
|
|
|
|
viewModel.handleAction(
|
|
|
|
activity,
|
|
|
|
EpisodeClickEvent(
|
|
|
|
storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER,
|
|
|
|
resume.result
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
result_resume_series_button?.setOnClickListener(click)
|
|
|
|
result_resume_series_button_play?.setOnClickListener(click)
|
2021-07-29 15:16:08 +00:00
|
|
|
}
|
2022-08-03 17:27:49 +00:00
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
observeNullable(viewModel.episodes) { episodes ->
|
2022-08-05 23:41:35 +00:00
|
|
|
updateEpisodes(episodes)
|
2021-05-22 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 02:46:43 +00:00
|
|
|
result_cast_items?.setOnFocusChangeListener { _, hasFocus ->
|
|
|
|
// Always escape focus
|
|
|
|
if (hasFocus) result_bookmark_button?.requestFocus()
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:05:34 +00:00
|
|
|
result_sync_set_score?.setOnClickListener {
|
2022-04-02 01:38:55 +00:00
|
|
|
syncModel.publishUserData()
|
2022-04-01 20:05:34 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 02:46:43 +00:00
|
|
|
observe(viewModel.trailers) { trailers ->
|
|
|
|
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
|
|
|
|
}
|
|
|
|
|
|
|
|
observe(viewModel.recommendations) { recommendations ->
|
|
|
|
setRecommendations(recommendations, null)
|
2021-07-17 14:14:25 +00:00
|
|
|
}
|
|
|
|
|
2023-07-13 21:18:37 +00:00
|
|
|
observeNullable(viewModel.movie) { data ->
|
2022-08-05 23:41:35 +00:00
|
|
|
updateMovie(data)
|
2022-08-03 00:04:03 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 01:00:48 +00:00
|
|
|
observe(viewModel.page) { data ->
|
2023-02-19 18:27:40 +00:00
|
|
|
if (data == null) return@observe
|
2021-05-18 13:43:32 +00:00
|
|
|
when (data) {
|
|
|
|
is Resource.Success -> {
|
|
|
|
val d = data.value
|
2021-09-19 22:36:32 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
updateVisStatus(2)
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2022-08-01 01:00:48 +00:00
|
|
|
result_vpn.setText(d.vpnText)
|
|
|
|
result_info.setText(d.metaText)
|
|
|
|
result_no_episodes.setText(d.noEpisodesFoundText)
|
|
|
|
result_title.setText(d.titleText)
|
|
|
|
result_meta_site.setText(d.apiName)
|
|
|
|
result_meta_type.setText(d.typeText)
|
|
|
|
result_meta_year.setText(d.yearText)
|
|
|
|
result_meta_duration.setText(d.durationText)
|
|
|
|
result_meta_rating.setText(d.ratingText)
|
|
|
|
result_cast_text.setText(d.actorsText)
|
2022-08-01 02:46:43 +00:00
|
|
|
result_next_airing.setText(d.nextAiringEpisode)
|
2022-08-01 01:00:48 +00:00
|
|
|
result_next_airing_time.setText(d.nextAiringDate)
|
|
|
|
result_poster.setImage(d.posterImage)
|
2022-11-01 17:01:29 +00:00
|
|
|
result_poster_background.setImage(d.posterBackgroundImage)
|
|
|
|
//result_trailer_thumbnail.setImage(d.posterBackgroundImage, fadeIn = false)
|
2022-08-03 00:04:03 +00:00
|
|
|
|
2022-08-28 23:52:15 +00:00
|
|
|
if (d.posterImage != null && !isTrueTvSettings())
|
2022-08-03 00:04:03 +00:00
|
|
|
result_poster_holder?.setOnClickListener {
|
|
|
|
try {
|
|
|
|
context?.let { ctx ->
|
|
|
|
runBlocking {
|
|
|
|
val sourceBuilder = AlertDialog.Builder(ctx)
|
|
|
|
sourceBuilder.setView(R.layout.result_poster)
|
|
|
|
|
|
|
|
val sourceDialog = sourceBuilder.create()
|
|
|
|
sourceDialog.show()
|
|
|
|
|
|
|
|
sourceDialog.findViewById<ImageView?>(R.id.imgPoster)
|
|
|
|
?.apply {
|
|
|
|
setImage(d.posterImage)
|
|
|
|
setOnClickListener {
|
|
|
|
sourceDialog.dismissSafe()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
}
|
|
|
|
}
|
2022-08-01 01:00:48 +00:00
|
|
|
|
2021-06-14 00:00:29 +00:00
|
|
|
|
2022-08-01 01:00:48 +00:00
|
|
|
result_cast_items?.isVisible = d.actors != null
|
2023-01-28 22:38:02 +00:00
|
|
|
(result_cast_items?.adapter as? ActorAdaptor)?.apply {
|
2022-08-01 01:00:48 +00:00
|
|
|
updateList(d.actors ?: emptyList())
|
|
|
|
}
|
2021-06-10 15:15:14 +00:00
|
|
|
|
2023-02-19 18:27:40 +00:00
|
|
|
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
|
|
|
|
result_subscribe?.isVisible = isSubscribed != null
|
|
|
|
if (isSubscribed == null) return@observeNullable
|
|
|
|
|
|
|
|
val drawable = if (isSubscribed) {
|
|
|
|
R.drawable.ic_baseline_notifications_active_24
|
|
|
|
} else {
|
|
|
|
R.drawable.baseline_notifications_none_24
|
|
|
|
}
|
|
|
|
|
|
|
|
result_subscribe?.setImageResource(drawable)
|
|
|
|
}
|
|
|
|
|
|
|
|
result_subscribe?.setOnClickListener {
|
|
|
|
val isSubscribed =
|
|
|
|
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
|
|
|
|
|
|
|
|
val message = if (isSubscribed) {
|
|
|
|
// Kinda icky to have this here, but it works.
|
|
|
|
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
|
|
|
R.string.subscription_new
|
|
|
|
} else {
|
|
|
|
R.string.subscription_deleted
|
|
|
|
}
|
|
|
|
|
|
|
|
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
|
|
|
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
|
|
|
showToast(activity, txt(message, name), Toast.LENGTH_SHORT)
|
|
|
|
}
|
|
|
|
|
2022-08-06 18:36:45 +00:00
|
|
|
result_open_in_browser?.isVisible = d.url.startsWith("http")
|
2022-04-03 01:13:02 +00:00
|
|
|
result_open_in_browser?.setOnClickListener {
|
|
|
|
val i = Intent(ACTION_VIEW)
|
|
|
|
i.data = Uri.parse(d.url)
|
|
|
|
try {
|
|
|
|
startActivity(i)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
2021-06-10 15:15:14 +00:00
|
|
|
}
|
2022-04-03 01:13:02 +00:00
|
|
|
}
|
2021-06-10 15:15:14 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
result_search?.setOnClickListener {
|
2022-08-01 01:00:48 +00:00
|
|
|
QuickSearchFragment.pushSearch(activity, d.title)
|
2022-04-03 01:13:02 +00:00
|
|
|
}
|
2021-11-20 00:41:37 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
result_share?.setOnClickListener {
|
|
|
|
try {
|
|
|
|
val i = Intent(ACTION_SEND)
|
|
|
|
i.type = "text/plain"
|
2022-08-01 01:00:48 +00:00
|
|
|
i.putExtra(EXTRA_SUBJECT, d.title)
|
2022-04-03 01:13:02 +00:00
|
|
|
i.putExtra(EXTRA_TEXT, d.url)
|
2022-08-01 01:00:48 +00:00
|
|
|
startActivity(createChooser(i, d.title))
|
2022-04-03 01:13:02 +00:00
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
2021-06-10 15:15:14 +00:00
|
|
|
}
|
2022-04-03 01:13:02 +00:00
|
|
|
}
|
2021-06-10 15:15:14 +00:00
|
|
|
|
2022-05-15 18:38:32 +00:00
|
|
|
if (syncModel.addSyncs(d.syncData)) {
|
|
|
|
syncModel.updateMetaAndUser()
|
|
|
|
syncModel.updateSynced()
|
|
|
|
} else {
|
|
|
|
syncModel.addFromUrl(d.url)
|
2022-04-03 21:41:28 +00:00
|
|
|
}
|
|
|
|
|
2022-08-02 00:43:42 +00:00
|
|
|
result_description.setTextHtml(d.plotText)
|
2022-08-06 16:08:20 +00:00
|
|
|
if (this !is ResultFragmentTv) // dont want this clickable on tv layout
|
|
|
|
result_description?.setOnClickListener { view ->
|
|
|
|
view.context?.let { ctx ->
|
|
|
|
val builder: AlertDialog.Builder =
|
|
|
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
|
|
|
builder.setMessage(d.plotText.asString(ctx).html())
|
|
|
|
.setTitle(d.plotHeaderText.asString(ctx))
|
|
|
|
.show()
|
|
|
|
}
|
2022-04-03 01:13:02 +00:00
|
|
|
}
|
2021-05-22 22:25:56 +00:00
|
|
|
|
2022-08-01 02:46:43 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
d.comingSoon.let { soon ->
|
|
|
|
result_coming_soon?.isVisible = soon
|
|
|
|
result_data_holder?.isGone = soon
|
|
|
|
}
|
2022-03-20 23:59:17 +00:00
|
|
|
|
2022-04-03 01:13:02 +00:00
|
|
|
val tags = d.tags
|
2022-08-01 01:00:48 +00:00
|
|
|
result_tag_holder?.isVisible = tags.isNotEmpty()
|
2022-10-28 16:43:14 +00:00
|
|
|
result_tag?.apply {
|
2022-12-28 11:51:55 +00:00
|
|
|
removeAllViews()
|
2022-10-28 16:43:14 +00:00
|
|
|
tags.forEach { tag ->
|
|
|
|
val chip = Chip(context)
|
|
|
|
val chipDrawable = ChipDrawable.createFromAttributes(
|
|
|
|
context,
|
|
|
|
null,
|
|
|
|
0,
|
|
|
|
R.style.ChipFilled
|
|
|
|
)
|
|
|
|
chip.setChipDrawable(chipDrawable)
|
|
|
|
chip.text = tag
|
2022-10-30 22:15:43 +00:00
|
|
|
chip.isChecked = false
|
2022-10-28 16:43:14 +00:00
|
|
|
chip.isCheckable = false
|
|
|
|
chip.isFocusable = false
|
|
|
|
chip.isClickable = false
|
2023-02-02 00:15:24 +00:00
|
|
|
chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
|
2022-10-28 16:43:14 +00:00
|
|
|
addView(chip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if (tags.isNotEmpty()) {
|
|
|
|
//result_tag_holder?.visibility = VISIBLE
|
|
|
|
//val isOnTv = isTrueTvSettings()
|
|
|
|
|
|
|
|
|
|
|
|
/*for ((index, tag) in tags.withIndex()) {
|
2022-04-03 01:13:02 +00:00
|
|
|
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
|
|
|
|
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
|
|
|
|
btt.text = tag
|
2022-04-03 21:41:28 +00:00
|
|
|
btt.isFocusable = !isOnTv
|
|
|
|
btt.isClickable = !isOnTv
|
2022-04-03 01:13:02 +00:00
|
|
|
result_tag?.addView(viewBtt, index)
|
2022-10-28 16:43:14 +00:00
|
|
|
}*/
|
|
|
|
//}
|
2021-05-18 13:43:32 +00:00
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2021-05-18 13:43:32 +00:00
|
|
|
is Resource.Failure -> {
|
2022-08-22 21:30:53 +00:00
|
|
|
result_error_text.text = storedData?.url?.plus("\n") + data.errorString
|
2021-06-16 16:54:07 +00:00
|
|
|
updateVisStatus(1)
|
|
|
|
}
|
2023-07-13 21:18:37 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
is Resource.Loading -> {
|
|
|
|
updateVisStatus(0)
|
2021-05-18 13:43:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-19 22:36:32 +00:00
|
|
|
context?.let { ctx ->
|
2022-08-04 01:19:59 +00:00
|
|
|
|
2022-08-20 01:06:35 +00:00
|
|
|
//result_bookmark_button?.isVisible = ctx.isTvSettings()
|
2021-12-10 19:48:21 +00:00
|
|
|
|
2021-09-19 22:36:32 +00:00
|
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
2022-08-22 21:30:53 +00:00
|
|
|
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2022-06-18 23:03:25 +00:00
|
|
|
Kitsu.isEnabled =
|
|
|
|
settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true)
|
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
if (storedData?.url != null) {
|
2021-09-19 22:36:32 +00:00
|
|
|
result_reload_connectionerror.setOnClickListener {
|
2022-09-22 17:38:45 +00:00
|
|
|
viewModel.load(
|
|
|
|
activity,
|
|
|
|
storedData.url,
|
|
|
|
storedData.apiName,
|
|
|
|
storedData.showFillers,
|
|
|
|
storedData.dubStatus,
|
|
|
|
storedData.start
|
|
|
|
)
|
2021-08-29 17:15:09 +00:00
|
|
|
}
|
2021-06-16 16:54:07 +00:00
|
|
|
|
2021-12-10 00:20:24 +00:00
|
|
|
result_reload_connection_open_in_browser?.setOnClickListener {
|
|
|
|
val i = Intent(ACTION_VIEW)
|
2022-08-22 21:30:53 +00:00
|
|
|
i.data = Uri.parse(storedData.url)
|
2022-03-14 12:44:54 +00:00
|
|
|
try {
|
|
|
|
startActivity(i)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
logError(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:30:53 +00:00
|
|
|
result_open_in_browser?.isVisible = storedData.url.startsWith("http")
|
2022-03-14 12:44:54 +00:00
|
|
|
result_open_in_browser?.setOnClickListener {
|
|
|
|
val i = Intent(ACTION_VIEW)
|
2022-08-22 21:30:53 +00:00
|
|
|
i.data = Uri.parse(storedData.url)
|
2021-12-10 00:20:24 +00:00
|
|
|
try {
|
|
|
|
startActivity(i)
|
|
|
|
} catch (e: Exception) {
|
2022-01-30 22:02:57 +00:00
|
|
|
logError(e)
|
2021-12-10 00:20:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 21:41:28 +00:00
|
|
|
// bloats the navigation on tv
|
2022-08-28 23:52:15 +00:00
|
|
|
if (!isTrueTvSettings()) {
|
2022-04-03 21:41:28 +00:00
|
|
|
result_meta_site?.setOnClickListener {
|
2022-08-22 21:30:53 +00:00
|
|
|
it.context?.openBrowser(storedData.url)
|
2022-04-03 21:41:28 +00:00
|
|
|
}
|
|
|
|
result_meta_site?.isFocusable = true
|
|
|
|
} else {
|
|
|
|
result_meta_site?.isFocusable = false
|
2021-09-19 22:36:32 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 02:46:43 +00:00
|
|
|
if (restart || !viewModel.hasLoaded()) {
|
2022-01-07 19:27:25 +00:00
|
|
|
//viewModel.clear()
|
2022-09-22 17:38:45 +00:00
|
|
|
viewModel.load(
|
|
|
|
activity,
|
|
|
|
storedData.url,
|
|
|
|
storedData.apiName,
|
|
|
|
storedData.showFillers,
|
|
|
|
storedData.dubStatus,
|
|
|
|
storedData.start
|
|
|
|
)
|
2021-09-12 15:57:07 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-16 16:54:07 +00:00
|
|
|
}
|
2022-02-04 20:49:35 +00:00
|
|
|
}
|
2022-03-20 23:25:25 +00:00
|
|
|
}
|