From a99713fe0c2f33f3ef0c519d1ba951f65d9dc4ce Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Wed, 3 Aug 2022 02:04:03 +0200 Subject: [PATCH] viewmodel dialogs --- .../com/lagradost/cloudstream3/MainAPI.kt | 1 + .../lagradost/cloudstream3/MainActivity.kt | 5 +- .../animeproviders/GogoanimeProvider.kt | 2 +- .../cloudstream3/mvvm/ArchComponentExt.kt | 26 ++ .../cloudstream3/ui/player/GeneratorPlayer.kt | 3 + .../cloudstream3/ui/result/ResultFragment.kt | 167 ++++++- .../ui/result/ResultViewModel2.kt | 436 +++++++++++++----- .../cloudstream3/ui/result/UiText.kt | 9 + .../lagradost/cloudstream3/utils/AppUtils.kt | 10 +- .../cloudstream3/utils/Coroutines.kt | 4 +- .../utils/SingleSelectionHelper.kt | 76 ++- app/src/main/res/layout/bottom_loading.xml | 33 ++ .../layout/bottom_selection_dialog_direct.xml | 34 ++ app/src/main/res/layout/fragment_result.xml | 1 - app/src/main/res/layout/result_poster.xml | 2 +- ...sort_bottom_single_choice_no_checkmark.xml | 22 + app/src/main/res/values/styles.xml | 16 +- 17 files changed, 655 insertions(+), 192 deletions(-) create mode 100644 app/src/main/res/layout/bottom_loading.xml create mode 100644 app/src/main/res/layout/bottom_selection_dialog_direct.xml create mode 100644 app/src/main/res/layout/sort_bottom_single_choice_no_checkmark.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 702ff6a1..b34fc6d7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -1114,6 +1114,7 @@ data class NextAiring( data class SeasonData( val season: Int, val name: String? = null, + val displaySeason : Int? = null, // will use season if null ) interface EpisodeResponse { diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 86a0aafe..8719936e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -332,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { if (str.contains(appString)) { for (api in OAuth2Apis) { if (str.contains("/${api.redirectUrl}")) { + val activity = this ioSafe { Log.i(TAG, "handleAppIntent $str") val isSuccessful = api.handleRedirect(str) @@ -342,10 +343,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { Log.i(TAG, "failed to authenticate ${api.name}") } - this.runOnUiThread { + activity.runOnUiThread { try { showToast( - this, + activity, getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format( api.name ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt index b1a9a629..7aaab91d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt @@ -54,7 +54,7 @@ class GogoanimeProvider : MainAPI() { secretKeyString: String, encrypt: Boolean = true ): String { - println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string") + //println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string") val ivParameterSpec = IvParameterSpec(iv.toByteArray()) val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 2a8c49f7..c831884f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -51,6 +51,32 @@ fun LifecycleOwner.observeDirectly(liveData: LiveData, action: (t: T) -> action(currentValue) } +inline fun some(value: T?): Some { + return if (value == null) { + Some.None + } else { + Some.Success(value) + } +} + +sealed class Some { + data class Success(val value: T) : Some() + object None : Some() + + override fun toString(): String { + return when(this) { + is None -> "None" + is Success -> "Some(${value.toString()})" + } + } +} + +sealed class ResourceSome { + data class Success(val value: T) : ResourceSome() + object None : ResourceSome() + data class Loading(val data: Any? = null) : ResourceSome() +} + sealed class Resource { data class Success(val value: T) : Resource() data class Failure( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 1973d7bd..d0b03774 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -125,6 +125,7 @@ class GeneratorPlayer : FullScreenPlayer() { private fun loadExtractorJob(extractorLink: ExtractorLink?) { currentVerifyLink?.cancel() + extractorLink?.let { currentVerifyLink = ioSafe { if (it.extractorData != null) { @@ -488,7 +489,9 @@ class GeneratorPlayer : FullScreenPlayer() { .setView(R.layout.player_select_source_and_subs) val sourceDialog = sourceBuilder.create() + selectSourceDialog = sourceDialog + sourceDialog.show() val providerList = sourceDialog.sort_providers val subtitleList = sourceDialog.sort_subtitles diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index b8dd6e8b..170e9da7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.result import android.annotation.SuppressLint +import android.app.Dialog import android.content.Intent import android.content.Intent.* import android.content.res.ColorStateList @@ -15,6 +16,7 @@ import android.view.View import android.view.ViewGroup import android.widget.AbsListView import android.widget.ArrayAdapter +import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone @@ -28,15 +30,13 @@ import com.discord.panels.PanelsChildGestureRegionObserver import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastState +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick @@ -54,8 +54,10 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard @@ -68,6 +70,7 @@ import kotlinx.android.synthetic.main.fragment_trailer.* import kotlinx.android.synthetic.main.result_recommendations.* import kotlinx.android.synthetic.main.result_sync.* import kotlinx.android.synthetic.main.trailer_custom_layout.* +import kotlinx.coroutines.runBlocking const val START_ACTION_NORMAL = 0 const val START_ACTION_RESUME_LATEST = 1 @@ -206,8 +209,6 @@ class ResultFragment : ResultTrailerPlayer() { private var updateUIListener: (() -> Unit)? = null } - private var currentLoadingCount = - 0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED private lateinit var viewModel: ResultViewModel2 //by activityViewModels() private lateinit var syncModel: SyncViewModel @@ -418,7 +419,8 @@ class ResultFragment : ResultTrailerPlayer() { viewModel.reloadEpisodes() } - var apiName: String = "" + var loadingDialog: Dialog? = null + var popupDialog: Dialog? = null @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -466,7 +468,7 @@ class ResultFragment : ResultTrailerPlayer() { // activity?.fixPaddingStatusbar(result_toolbar) val url = arguments?.getString(URL_BUNDLE) - apiName = arguments?.getString(API_NAME_BUNDLE) ?: return + val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return startAction = arguments?.getInt(START_ACTION_BUNDLE) ?: START_ACTION_NORMAL startValue = arguments?.getInt(START_VALUE_BUNDLE) val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE) @@ -862,16 +864,16 @@ class ResultFragment : ResultTrailerPlayer() { */ observe(viewModel.episodes) { episodes -> when (episodes) { - is Resource.Failure -> { + is ResourceSome.None -> { result_episode_loading?.isVisible = false - //result_episodes?.isVisible = false + result_episodes?.isVisible = false } - is Resource.Loading -> { + is ResourceSome.Loading -> { result_episode_loading?.isVisible = true - // result_episodes?.isVisible = false + result_episodes?.isVisible = false } - is Resource.Success -> { - //result_episodes?.isVisible = true + is ResourceSome.Success -> { + result_episodes?.isVisible = true result_episode_loading?.isVisible = false (result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value) } @@ -879,7 +881,7 @@ class ResultFragment : ResultTrailerPlayer() { } observe(viewModel.selectedSeason) { text -> - result_season_button?.setText(text) + result_season_button.setText(text) // If the season button is visible the result season button will be next focus down if (result_season_button?.isVisible == true) @@ -901,6 +903,70 @@ class ResultFragment : ResultTrailerPlayer() { } } + observe(viewModel.selectPopup) { popup -> + println("POPUPSTATUS:$popup") + when (popup) { + is Some.Success -> { + popupDialog?.dismissSafe(activity) + + popupDialog = activity?.let { act -> + val pop = popup.value + val options = pop.getOptions(act) + val title = pop.getTitle(act) + + act.showBottomDialogInstant( + options, title, { + popupDialog = null + pop.callback(context ?: return@showBottomDialogInstant, null) + }, { + popupDialog = null + pop.callback(context ?: return@showBottomDialogInstant, it) + } + ) + } + } + is Some.None -> { + popupDialog?.dismissSafe(activity) + popupDialog = null + } + } + + //showBottomDialogInstant + } + + observe(viewModel.loadedLinks) { load -> + + when (load) { + is Some.Success -> { + if(loadingDialog?.isShowing != true) { + loadingDialog?.dismissSafe(activity) + loadingDialog = null + } + loadingDialog = loadingDialog ?: context?.let { ctx -> + val builder = + BottomSheetDialog(ctx) + builder.setContentView(R.layout.bottom_loading) + builder.setOnDismissListener { + loadingDialog = null + viewModel.cancelLinks() + } + //builder.setOnCancelListener { + // it?.dismiss() + //} + builder.setCanceledOnTouchOutside(true) + + builder.show() + + builder + } + } + is Some.None -> { + loadingDialog?.dismissSafe(activity) + loadingDialog = null + } + } + } + observe(viewModel.selectedRange) { range -> result_episode_select.setText(range) @@ -933,7 +999,8 @@ class ResultFragment : ResultTrailerPlayer() { } observe(viewModel.rangeSelections) { range -> - result_episode_select.setOnClickListener { view -> + println("RANGE:$range") + result_episode_select?.setOnClickListener { view -> view?.context?.let { ctx -> val names = range .mapNotNull { (text, r) -> @@ -987,6 +1054,33 @@ class ResultFragment : ResultTrailerPlayer() { setRecommendations(recommendations, null) } + observe(viewModel.movie) { data -> + when (data) { + is ResourceSome.Success -> { + 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 + } + } + } + else -> { + result_play_movie?.isVisible = false + + } + } + } + observe(viewModel.page) { data -> when (data) { is Resource.Success -> { @@ -1006,9 +1100,32 @@ class ResultFragment : ResultTrailerPlayer() { result_cast_text.setText(d.actorsText) result_next_airing.setText(d.nextAiringEpisode) result_next_airing_time.setText(d.nextAiringDate) - result_poster.setImage(d.posterImage) - result_play_movie.setText(d.playMovieText) + + if (d.posterImage != null && context?.isTrueTvSettings() == false) + 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(R.id.imgPoster) + ?.apply { + setImage(d.posterImage) + setOnClickListener { + sourceDialog.dismissSafe() + } + } + } + } + } catch (e: Exception) { + logError(e) + } + } result_cast_items?.isVisible = d.actors != null @@ -1016,6 +1133,7 @@ class ResultFragment : ResultTrailerPlayer() { updateList(d.actors ?: emptyList()) } + result_open_in_browser?.isGone = d.url.isBlank() result_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) i.data = Uri.parse(d.url) @@ -1238,15 +1356,14 @@ class ResultFragment : ResultTrailerPlayer() { Kitsu.isEnabled = settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true) - val tempUrl = url - if (tempUrl != null) { + if (url != null) { result_reload_connectionerror.setOnClickListener { - viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX + viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX } result_reload_connection_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) - i.data = Uri.parse(tempUrl) + i.data = Uri.parse(url) try { startActivity(i) } catch (e: Exception) { @@ -1256,7 +1373,7 @@ class ResultFragment : ResultTrailerPlayer() { result_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) - i.data = Uri.parse(tempUrl) + i.data = Uri.parse(url) try { startActivity(i) } catch (e: Exception) { @@ -1267,7 +1384,7 @@ class ResultFragment : ResultTrailerPlayer() { // bloats the navigation on tv if (context?.isTrueTvSettings() == false) { result_meta_site?.setOnClickListener { - it.context?.openBrowser(tempUrl) + it.context?.openBrowser(url) } result_meta_site?.isFocusable = true } else { @@ -1276,7 +1393,7 @@ class ResultFragment : ResultTrailerPlayer() { if (restart || !viewModel.hasLoaded()) { //viewModel.clear() - viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX + viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 544445a3..d6ee4a31 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -36,17 +36,16 @@ import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.CastHelper.startCast import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.setKey +import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.UIHelper.checkWrite import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import java.io.File import java.util.concurrent.TimeUnit @@ -86,7 +85,6 @@ data class ResultData( val yearText: UiText?, val nextAiringDate: UiText?, val nextAiringEpisode: UiText?, - val playMovieText: UiText?, val plotHeaderText: UiText, ) @@ -118,7 +116,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24 val minute = TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60 - nextAiringEpisode = when { + nextAiringDate = when { days > 0 -> { txt( R.string.next_episode_time_day_format, @@ -138,11 +136,11 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ) else -> null }?.also { - nextAiringDate = txt(R.string.next_episode_format, airing.episode) + nextAiringEpisode = txt(R.string.next_episode_format, airing.episode) } } } - + val dur = duration return ResultData( syncData = syncData, plotHeaderText = txt( @@ -151,14 +149,6 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { else -> R.string.result_plot } ), - playMovieText = txt( - when (this.type) { - TvType.Live -> R.string.play_livestream_button - TvType.Torrent -> R.string.play_torrent_button - TvType.Movie, TvType.AnimeMovie -> R.string.play_movie_button - else -> null - } - ), nextAiringDate = nextAiringDate, nextAiringEpisode = nextAiringEpisode, posterImage = img( @@ -192,7 +182,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { TvType.Live -> R.string.live_singular } ), - yearText = txt(year), + yearText = txt(year?.toString()), apiName = txt(apiName), ratingText = rating?.div(1000f)?.let { txt(R.string.rating_format, it) }, vpnText = txt( @@ -204,7 +194,10 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ), metaText = if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null, - durationText = txt(R.string.duration_format, duration), + durationText = if (dur == null || dur <= 0) null else txt( + R.string.duration_format, + dur + ), onGoingText = if (this is EpisodeResponse) { txt( when (showStatus) { @@ -245,21 +238,43 @@ sealed class SelectPopup { val map: Int?, val callback: (Int?) -> Unit ) : SelectPopup() +} - fun SelectPopup.transformResult(context: Context, input: Int?): Int? { - if (input == null) return null - return when (this) { - is SelectArray -> context.resources.getIntArray(map ?: return input).getOrNull(input) - ?: input - is SelectText -> input - } +fun SelectPopup.callback(context: Context, input: Int?) { + val ret = transformResult(context, input) + return when (this) { + is SelectPopup.SelectArray -> callback(ret) + is SelectPopup.SelectText -> callback(ret) } +} - fun SelectPopup.getOptions(context: Context): List { - return when (this) { - is SelectArray -> context.resources.getStringArray(options).toList() - is SelectText -> options.map { it.asString(context) } +fun SelectPopup.transformResult(context: Context, input: Int?): Int? { + if (input == null) return null + return when (this) { + is SelectPopup.SelectArray -> context.resources.getIntArray(map ?: return input) + .getOrNull(input) + ?: input + is SelectPopup.SelectText -> input + } +} + +fun SelectPopup.getTitle(context: Context): String { + return when (this) { + is SelectPopup.SelectArray -> text.asString(context) + is SelectPopup.SelectText -> text.asString(context) + } +} + +fun SelectPopup.getOptions(context: Context): List { + return when (this) { + is SelectPopup.SelectArray -> { + val cmap = this.map?.let { context.resources.getIntArray(it) } + context.resources.getStringArray(options).toList().filterIndexed { index, s -> + + true + } } + is SelectPopup.SelectText -> options.map { it.asString(context) } } } @@ -274,6 +289,8 @@ class ResultViewModel2 : ViewModel() { /** map>> */ private var currentEpisodes: Map> = mapOf() private var currentRanges: Map> = mapOf() + private var currentSeasons: Set = setOf() + private var currentDubStatus: Set = setOf() private var currentMeta: SyncAPI.SyncResult? = null private var currentSync: Map? = null private var currentIndex: EpisodeIndexer? = null @@ -294,13 +311,17 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(Resource.Loading()) val page: LiveData> = _page - private val _episodes: MutableLiveData>> = - MutableLiveData(Resource.Loading()) - val episodes: LiveData>> = _episodes + private val _episodes: MutableLiveData>> = + MutableLiveData(ResourceSome.Loading()) + val episodes: LiveData>> = _episodes - private val _episodesCountText: MutableLiveData = - MutableLiveData(null) - val episodesCountText: LiveData = _episodesCountText + private val _movie: MutableLiveData>> = + MutableLiveData(ResourceSome.None) + val movie: LiveData>> = _movie + + private val _episodesCountText: MutableLiveData> = + MutableLiveData(Some.None) + val episodesCountText: LiveData> = _episodesCountText private val _trailers: MutableLiveData> = MutableLiveData(mutableListOf()) val trailers: LiveData> = _trailers @@ -318,24 +339,23 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(emptyList()) val seasonSelections: LiveData>> = _seasonSelections - private val _recommendations: MutableLiveData> = MutableLiveData(emptyList()) val recommendations: LiveData> = _recommendations - private val _selectedRange: MutableLiveData = - MutableLiveData(null) - val selectedRange: LiveData = _selectedRange + private val _selectedRange: MutableLiveData> = + MutableLiveData(Some.None) + val selectedRange: LiveData> = _selectedRange - private val _selectedSeason: MutableLiveData = - MutableLiveData(null) - val selectedSeason: LiveData = _selectedSeason + private val _selectedSeason: MutableLiveData> = + MutableLiveData(Some.None) + val selectedSeason: LiveData> = _selectedSeason - private val _selectedDubStatus: MutableLiveData = MutableLiveData(null) - val selectedDubStatus: LiveData = _selectedDubStatus + private val _selectedDubStatus: MutableLiveData> = MutableLiveData(Some.None) + val selectedDubStatus: LiveData> = _selectedDubStatus - private val _loadedLinks: MutableLiveData = MutableLiveData(null) - val loadedLinks: LiveData = _loadedLinks + private val _loadedLinks: MutableLiveData> = MutableLiveData(Some.None) + val loadedLinks: LiveData> = _loadedLinks companion object { const val TAG = "RVM2" @@ -680,8 +700,8 @@ class ResultViewModel2 : ViewModel() { private val _watchStatus: MutableLiveData = MutableLiveData(WatchType.NONE) val watchStatus: LiveData get() = _watchStatus - private val _selectPopup: MutableLiveData = MutableLiveData(null) - val selectPopup: LiveData get() = _selectPopup + private val _selectPopup: MutableLiveData> = MutableLiveData(Some.None) + val selectPopup: LiveData> get() = _selectPopup fun updateWatchStatus(status: WatchType) { val currentId = currentId ?: return @@ -707,14 +727,15 @@ class ResultViewModel2 : ViewModel() { ) } - private suspend fun startChromecast( + private fun startChromecast( activity: Activity?, result: ResultEpisode, isVisible: Boolean = true ) { if (activity == null) return - val data = loadLinks(result, isVisible = isVisible, isCasting = true) - startChromecast(activity, result, data.links, data.subs, 0) + loadLinks(result, isVisible = isVisible, isCasting = true) { data -> + startChromecast(activity, result, data.links, data.subs, 0) + } } private fun startChromecast( @@ -742,51 +763,100 @@ class ResultViewModel2 : ViewModel() { ) } - private val popupCallback: ((Int) -> Unit)? = null - fun cancelLinks() { + println("called::cancelLinks") currentLoadLinkJob?.cancel() - _loadedLinks.postValue(null) + currentLoadLinkJob = null + _loadedLinks.postValue(Some.None) + } + + private fun postPopup(text: UiText, options: List, callback: suspend (Int?) -> Unit) { + _selectPopup.postValue( + some(SelectPopup.SelectText( + text, + options + ) { value -> + viewModelScope.launch { + _selectPopup.postValue(Some.None) + callback.invoke(value) + } + }) + ) + } + + private fun postPopup( + text: UiText, + options: Int, + values: Int, + callback: suspend (Int?) -> Unit + ) { + _selectPopup.postValue( + some(SelectPopup.SelectArray( + text, + options, + values + ) { value -> + viewModelScope.launch { + _selectPopup.value = Some.None + callback.invoke(value) + } + }) + ) + } + + fun loadLinks( + result: ResultEpisode, + isVisible: Boolean, + isCasting: Boolean, + clearCache: Boolean = false, + work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) + ) { + currentLoadLinkJob?.cancel() + currentLoadLinkJob = ioSafe { + val links = loadLinks( + result, + isVisible = isVisible, + isCasting = isCasting, + clearCache = clearCache + ) + if (!this.isActive) return@ioSafe + work(links) + } } private var currentLoadLinkJob: Job? = null - private suspend fun acquireSingleLink( + private fun acquireSingleLink( result: ResultEpisode, isCasting: Boolean, text: UiText, callback: (Pair) -> Unit, ) { - currentLoadLinkJob = viewModelScope.launch { - val links = loadLinks(result, isVisible = true, isCasting = isCasting) - - _selectPopup.postValue( - SelectPopup.SelectText( - text, - links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) { - callback.invoke(links to (it ?: return@SelectText)) - }) + loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + postPopup( + text, + links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) { + callback.invoke(links to (it ?: return@postPopup)) + } } } - private suspend fun acquireSingleSubtitle( + private fun acquireSingleSubtitle( result: ResultEpisode, isCasting: Boolean, text: UiText, callback: (Pair) -> Unit, ) { - currentLoadLinkJob = viewModelScope.launch { - val links = loadLinks(result, isVisible = true, isCasting = isCasting) - - _selectPopup.postValue( - SelectPopup.SelectText( - text, - links.subs.map { txt(it.name) }) { - callback.invoke(links to (it ?: return@SelectText)) - }) + loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + postPopup( + text, + links.subs.map { txt(it.name) }) + { + callback.invoke(links to (it ?: return@postPopup)) + } } } - suspend fun loadLinks( + suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, isCasting: Boolean, @@ -797,11 +867,12 @@ class ResultViewModel2 : ViewModel() { val links: MutableSet = mutableSetOf() val subs: MutableSet = mutableSetOf() fun updatePage() { - if (isVisible) { - _loadedLinks.postValue(LinkProgress(links.size, subs.size)) + if (isVisible && isActive) { + _loadedLinks.postValue(some(LinkProgress(links.size, subs.size))) } } try { + updatePage() tempGenerator.generateLinks(clearCache, isCasting, { (link, _) -> if (link != null) { links += link @@ -814,7 +885,7 @@ class ResultViewModel2 : ViewModel() { } catch (e: Exception) { logError(e) } finally { - _loadedLinks.postValue(null) + _loadedLinks.postValue(Some.None) } return LinkLoadingResult(sortUrls(links), sortSubs(subs)) @@ -884,20 +955,22 @@ class ResultViewModel2 : ViewModel() { private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { - _selectPopup.postValue( - SelectPopup.SelectArray( - txt(""), // TODO FIX - R.array.episode_long_click_options, - R.array.episode_long_click_options_values - ) { result -> - if (result == null) return@SelectArray - viewModelScope.launch { - handleEpisodeClickEvent( - activity, - click.copy(action = result) - ) - } - }) + postPopup( + txt( + activity?.getNameFull( + click.data.name, + click.data.episode, + click.data.season + ) ?: "" + ), // TODO FIX + R.array.episode_long_click_options, + R.array.episode_long_click_options_values + ) { result -> + handleEpisodeClickEvent( + activity, + click.copy(action = result ?: return@postPopup) + ) + } } ACTION_CLICK_DEFAULT -> { activity?.let { ctx -> @@ -986,7 +1059,14 @@ class ResultViewModel2 : ViewModel() { } } ACTION_RELOAD_EPISODE -> { - loadLinks(click.data, isVisible = false, isCasting = false, clearCache = true) + ioSafe { + loadLinks( + click.data, + isVisible = false, + isCasting = false, + clearCache = true + ) + } } ACTION_CHROME_CAST_MIRROR -> { acquireSingleLink( @@ -1030,8 +1110,12 @@ class ResultViewModel2 : ViewModel() { startChromecast(activity, click.data) } ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - currentLoadLinkJob = viewModelScope.launch { - playWithVlc(activity, loadLinks(click.data, true, true), click.data.id) + loadLinks(click.data, isVisible = true, isCasting = true) { links -> + playWithVlc( + activity, + links, + click.data.id + ) } } ACTION_PLAY_EPISODE_IN_PLAYER -> { @@ -1176,6 +1260,13 @@ class ResultViewModel2 : ViewModel() { postEpisodeRange(currentIndex?.copy(season = season), currentRange) } + private fun getMovie(): ResultEpisode? { + return currentEpisodes.entries.firstOrNull()?.value?.firstOrNull()?.let { ep -> + val posDur = DataStoreHelper.getViewPos(ep.id) + ep.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0) + } + } + private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List { val startIndex = range.startIndex val length = range.length @@ -1192,15 +1283,50 @@ class ResultViewModel2 : ViewModel() { ?: emptyList() } + private fun postMovie() { + val response = currentResponse + _episodes.postValue(ResourceSome.None) + + if (response == null) { + _movie.postValue(ResourceSome.None) + return + } + + val text = txt( + when (response.type) { + TvType.Torrent -> R.string.play_torrent_button + else -> { + if (response.type.isLiveStream()) + R.string.play_livestream_button + else if (response.type.isMovieType()) // this wont break compatibility as you only need to override isMovieType + R.string.play_movie_button + else null + } + } + ) + val data = getMovie() + _episodes.postValue(ResourceSome.None) + if (text == null || data == null) { + _movie.postValue(ResourceSome.None) + } else { + _movie.postValue(ResourceSome.Success(text to data)) + } + } + fun reloadEpisodes() { - _episodes.postValue( - Resource.Success( - getEpisodes( - currentIndex ?: return, - currentRange ?: return + if (currentResponse?.isMovie() == true) { + postMovie() + } else { + _episodes.postValue( + ResourceSome.Success( + getEpisodes( + currentIndex ?: return, + currentRange ?: return + ) ) ) - ) + _movie.postValue(ResourceSome.None) + } } private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { @@ -1208,45 +1334,79 @@ class ResultViewModel2 : ViewModel() { return } - val size = currentEpisodes[indexer]?.size - - _episodesCountText.postValue( - txt( - R.string.episode_format, - txt(if (size == 1) R.string.episode else R.string.episodes), - size - ) - ) - + val episodes = currentEpisodes[indexer] + val ranges = currentRanges[indexer] + val size = episodes?.size + val isMovie = currentResponse?.isMovie() == true currentIndex = indexer currentRange = range + + _rangeSelections.postValue(ranges?.map { r -> + val text = txt(R.string.episodes_range, r.startEpisode, r.endEpisode) + text to r + } ?: emptyList()) + + _episodesCountText.postValue( + some( + if (isMovie) null else + txt( + R.string.episode_format, + size, + txt(if (size == 1) R.string.episode else R.string.episodes), + ) + ) + ) + _selectedSeason.postValue( - when (indexer.season) { - 0 -> txt(R.string.no_season) - else -> txt(R.string.season_format, R.string.season, indexer.season) //TODO FIX - } + some( + if (isMovie || currentSeasons.size <= 1) null else + when (indexer.season) { + 0 -> txt(R.string.no_season) + else -> txt( + R.string.season_format, + txt(R.string.season), + indexer.season + ) //TODO FIX DISPLAYNAME + } + ) ) + _selectedRange.postValue( - if ((currentRanges[indexer]?.size ?: 0) > 1) { - txt(R.string.episodes_range, range.startEpisode, range.endEpisode) - } else { - null - } + some( + if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) { + txt(R.string.episodes_range, range.startEpisode, range.endEpisode) + } else { + null + } + ) + ) + _selectedDubStatus.postValue( + some( + if (isMovie || currentDubStatus.size <= 1) null else + txt(indexer.dubStatus) + ) ) - _selectedDubStatus.postValue(txt(indexer.dubStatus)) //TODO SET KEYS preferStartEpisode = range.startEpisode preferStartSeason = indexer.season preferDubStatus = indexer.dubStatus - generator = currentEpisodes[indexer]?.let { list -> - RepoLinkGenerator(list) + generator = if (isMovie) { + getMovie()?.let { RepoLinkGenerator(listOf(it)) } + } else { + episodes?.let { list -> + RepoLinkGenerator(list) + } } - val ret = getEpisodes(indexer, range) - _episodes.postValue(Resource.Success(ret)) + if (isMovie) { + postMovie() + } else { + val ret = getEpisodes(indexer, range) + _episodes.postValue(ResourceSome.Success(ret)) + } } private suspend fun postSuccessful( @@ -1262,11 +1422,13 @@ class ResultViewModel2 : ViewModel() { } private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) { - _episodes.postValue(Resource.Loading()) + _episodes.postValue(ResourceSome.Loading()) val mainId = loadResponse.getId() currentId = mainId + _watchStatus.postValue(getResultWatchState(mainId)) + if (updateFillers && loadResponse is AnimeLoadResponse) { updateFillers(loadResponse.name) } @@ -1425,10 +1587,30 @@ class ResultViewModel2 : ViewModel() { } } + val seasonsSelection = mutableSetOf() + val dubSelection = mutableSetOf() + allEpisodes.keys.forEach { key -> + seasonsSelection += key.season + dubSelection += key.dubStatus + } + currentDubStatus = dubSelection + currentSeasons = seasonsSelection + _dubSubSelections.postValue(dubSelection.map { txt(it) to it }) + if (loadResponse is EpisodeResponse) { + _seasonSelections.postValue(seasonsSelection.map { seasonNumber -> + val name = + loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData -> + txt(seasonData) + } ?: txt(R.string.season_format, txt(R.string.season), seasonNumber) + name to seasonNumber + }) + } + currentEpisodes = allEpisodes val ranges = getRanges(allEpisodes) currentRanges = ranges + // this takes the indexer most preferable by the user given the current sorting val min = ranges.keys.minByOrNull { index -> kotlin.math.abs( @@ -1464,7 +1646,7 @@ class ResultViewModel2 : ViewModel() { ) = viewModelScope.launch { _page.postValue(Resource.Loading(url)) - _episodes.postValue(Resource.Loading(url)) + _episodes.postValue(ResourceSome.Loading()) preferDubStatus = dubStatus currentShowFillers = showFillers diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt index 6ec18b3a..03212133 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt @@ -8,6 +8,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.view.isGone import androidx.core.view.isVisible +import com.lagradost.cloudstream3.mvvm.Some import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.UIHelper.setImage @@ -152,3 +153,11 @@ fun TextView?.setTextHtml(text: UiText?) { this.text = str.html() } } + +fun TextView?.setTextHtml(text: Some) { + setTextHtml(if(text is Some.Success) text.value else null) +} + +fun TextView?.setText(text: Some) { + setText(if(text is Some.Success) text.value else null) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 5b7223d3..b543fb66 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -179,21 +179,21 @@ object AppUtils { @WorkerThread fun Context.addProgramsToContinueWatching(data: List) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - + val context = this ioSafe { data.forEach { episodeInfo -> try { - val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this) - val nextProgram = buildWatchNextProgramUri(this, episodeInfo) + val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context) + val nextProgram = buildWatchNextProgramUri(context, episodeInfo) // If the program is already in the Watch Next row, update it if (program != null && id != null) { - PreviewChannelHelper(this).updateWatchNextProgram( + PreviewChannelHelper(context).updateWatchNextProgram( nextProgram, id, ) } else { - PreviewChannelHelper(this) + PreviewChannelHelper(context) .publishWatchNextProgram(nextProgram) } } catch (e: Exception) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt index d5cb06f7..978b2720 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -12,7 +12,7 @@ object Coroutines { } } - fun ioSafe(work: suspend (() -> Unit)): Job { + fun ioSafe(work: suspend (CoroutineScope.() -> Unit)): Job { return CoroutineScope(Dispatchers.IO).launch { try { work() @@ -22,7 +22,7 @@ object Coroutines { } } - suspend fun ioWork(work: suspend (() -> T)): T { + suspend fun ioWork(work: suspend (CoroutineScope.() -> T)): T { return withContext(Dispatchers.IO) { work() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 814cf95b..b228fe77 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -12,6 +12,9 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.setImage +import kotlinx.android.synthetic.main.add_account_input.* +import kotlinx.android.synthetic.main.add_account_input.text1 +import kotlinx.android.synthetic.main.bottom_selection_dialog_direct.* object SingleSelectionHelper { fun Activity?.showOptionSelectStringRes( @@ -21,7 +24,7 @@ object SingleSelectionHelper { tvOptions: List = listOf(), callback: (Pair) -> Unit ) { - if(this == null) return + if (this == null) return this.showOptionSelect( view, @@ -39,7 +42,7 @@ object SingleSelectionHelper { tvOptions: List, callback: (Pair) -> Unit ) { - if(this == null) return + if (this == null) return if (this.isTvSettings()) { val builder = @@ -86,42 +89,44 @@ object SingleSelectionHelper { showApply: Boolean, isMultiSelect: Boolean, callback: (List) -> Unit, - dismissCallback: () -> Unit + dismissCallback: () -> Unit, + itemLayout: Int = R.layout.sort_bottom_single_choice ) { - if(this == null) return + if (this == null) return val realShowApply = showApply || isMultiSelect - val listView = dialog.findViewById(R.id.listview1)!! - val textView = dialog.findViewById(R.id.text1)!! - val applyButton = dialog.findViewById(R.id.apply_btt)!! - val cancelButton = dialog.findViewById(R.id.cancel_btt)!! - val applyHolder = dialog.findViewById(R.id.apply_btt_holder)!! + val listView = dialog.listview1//.findViewById(R.id.listview1)!! + val textView = dialog.text1//.findViewById(R.id.text1)!! + val applyButton = dialog.apply_btt//.findViewById(R.id.apply_btt) + val cancelButton = dialog.cancel_btt//findViewById(R.id.cancel_btt) + val applyHolder = dialog.apply_btt_holder//.findViewById(R.id.apply_btt_holder) - applyHolder.isVisible = realShowApply + applyHolder?.isVisible = realShowApply if (!realShowApply) { val params = listView.layoutParams as LinearLayout.LayoutParams params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0) listView.layoutParams = params } - textView.text = name + textView?.text = name + textView?.isGone = name.isBlank() - val arrayAdapter = ArrayAdapter(this, R.layout.sort_bottom_single_choice) + val arrayAdapter = ArrayAdapter(this, itemLayout) arrayAdapter.addAll(items) - listView.adapter = arrayAdapter + listView?.adapter = arrayAdapter if (isMultiSelect) { - listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE + listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE } else { - listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE + listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE } for (select in selectedIndex) { - listView.setItemChecked(select, true) + listView?.setItemChecked(select, true) } selectedIndex.minOrNull()?.let { - listView.setSelection(it) + listView?.setSelection(it) } // var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1 @@ -130,7 +135,7 @@ object SingleSelectionHelper { dismissCallback.invoke() } - listView.setOnItemClickListener { _, _, which, _ -> + listView?.setOnItemClickListener { _, _, which, _ -> // lastSelectedIndex = which if (realShowApply) { if (!isMultiSelect) { @@ -142,7 +147,7 @@ object SingleSelectionHelper { } } if (realShowApply) { - applyButton.setOnClickListener { + applyButton?.setOnClickListener { val list = ArrayList() for (index in 0 until listView.count) { if (listView.checkedItemPositions[index]) @@ -151,7 +156,7 @@ object SingleSelectionHelper { callback.invoke(list) dialog.dismissSafe(this) } - cancelButton.setOnClickListener { + cancelButton?.setOnClickListener { dialog.dismissSafe(this) } } @@ -166,7 +171,7 @@ object SingleSelectionHelper { callback: (String) -> Unit, dismissCallback: () -> Unit ) { - if(this == null) return + if (this == null) return val inputView = dialog.findViewById(R.id.nginx_text_input)!! val textView = dialog.findViewById(R.id.text1)!! @@ -205,7 +210,7 @@ object SingleSelectionHelper { dismissCallback: () -> Unit, callback: (List) -> Unit, ) { - if(this == null) return + if (this == null) return val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -224,7 +229,7 @@ object SingleSelectionHelper { dismissCallback: () -> Unit, callback: (Int) -> Unit, ) { - if(this == null) return + if (this == null) return val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -271,6 +276,31 @@ object SingleSelectionHelper { ) } + fun Activity.showBottomDialogInstant( + items: List, + name: String, + dismissCallback: () -> Unit, + callback: (Int) -> Unit, + ): BottomSheetDialog { + val builder = + BottomSheetDialog(this) + builder.setContentView(R.layout.bottom_selection_dialog_direct) + + builder.show() + showDialog( + builder, + items, + listOf(), + name, + showApply = false, + isMultiSelect = false, + callback = { if (it.isNotEmpty()) callback.invoke(it.first()) }, + dismissCallback = dismissCallback, + itemLayout = R.layout.sort_bottom_single_choice_no_checkmark + ) + return builder + } + fun Activity.showNginxTextInputDialog( name: String, value: String, diff --git a/app/src/main/res/layout/bottom_loading.xml b/app/src/main/res/layout/bottom_loading.xml new file mode 100644 index 00000000..6f9def0b --- /dev/null +++ b/app/src/main/res/layout/bottom_loading.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/app/src/main/res/layout/bottom_selection_dialog_direct.xml b/app/src/main/res/layout/bottom_selection_dialog_direct.xml new file mode 100644 index 00000000..0d179ebb --- /dev/null +++ b/app/src/main/res/layout/bottom_selection_dialog_direct.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 11513553..c9c218c2 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -843,7 +843,6 @@ diff --git a/app/src/main/res/layout/result_poster.xml b/app/src/main/res/layout/result_poster.xml index 1345d279..0675da7e 100644 --- a/app/src/main/res/layout/result_poster.xml +++ b/app/src/main/res/layout/result_poster.xml @@ -12,6 +12,6 @@ android:scaleType="fitCenter" android:adjustViewBounds="true" android:src="@drawable/default_cover" - android:background="#fffff0" + android:background="?attr/primaryGrayBackground" android:contentDescription="@string/poster_image" /> \ No newline at end of file diff --git a/app/src/main/res/layout/sort_bottom_single_choice_no_checkmark.xml b/app/src/main/res/layout/sort_bottom_single_choice_no_checkmark.xml new file mode 100644 index 00000000..0938ad1f --- /dev/null +++ b/app/src/main/res/layout/sort_bottom_single_choice_no_checkmark.xml @@ -0,0 +1,22 @@ + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3f2cc6c0..9da8d721 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -448,7 +448,15 @@ ?attr/textColor - + + + +