diff --git a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt index bbef5961..d811053e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3 import android.Manifest +import android.annotation.SuppressLint import android.app.Activity import android.app.AppOpsManager import android.content.Context @@ -11,13 +12,19 @@ import android.media.AudioAttributes import android.media.AudioFocusRequest import android.media.AudioManager import android.os.Build +import android.view.Gravity +import android.view.MenuItem import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.appcompat.view.menu.MenuBuilder +import androidx.appcompat.widget.PopupMenu import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.view.forEach import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastContext @@ -112,12 +119,11 @@ object UIHelper { ) } } - private var _AudioFocusRequest: AudioFocusRequest? = null private var _OnAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null var onAudioFocusEvent = Event() - fun getAudioListener(): AudioManager.OnAudioFocusChangeListener? { + private fun getAudioListener(): AudioManager.OnAudioFocusChangeListener? { if (_OnAudioFocusChangeListener != null) return _OnAudioFocusChangeListener _OnAudioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { onAudioFocusEvent.invoke( @@ -302,4 +308,50 @@ object UIHelper { val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) } + + + @SuppressLint("RestrictedApi") + inline fun View.popupMenu( + items: List>, + noinline onMenuItemClick: MenuItem.() -> Unit, + ): PopupMenu { + val ctw = ContextThemeWrapper(context, R.style.PopupMenu) + val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) + + items.forEach { (id, icon, stringRes) -> + popup.menu.add(0, id, 0, stringRes).setIcon(icon) + } + + (popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true) + + popup.setOnMenuItemClickListener { + it.onMenuItemClick() + true + } + + popup.show() + return popup + } + + inline fun View.popupMenuNoIcons( + items: List>, + noinline onMenuItemClick: MenuItem.() -> Unit, + ): PopupMenu { + val ctw = ContextThemeWrapper(context, R.style.PopupMenu) + val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) + + items.forEach { (id, stringRes) -> + popup.menu.add(0, id, 0, stringRes) + } + + (popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true) + + popup.setOnMenuItemClickListener { + it.onMenuItemClick() + true + } + + popup.show() + return popup + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt new file mode 100644 index 00000000..417b4408 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt @@ -0,0 +1,19 @@ +package com.lagradost.cloudstream3.ui + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import com.lagradost.cloudstream3.R + +enum class WatchType(val internalId: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) { + // FIX ICONS + WATCHING(0, R.string.type_watching, R.drawable.ic_baseline_remove_red_eye_24), + COMPLETED(1, R.string.type_completed, R.drawable.ic_baseline_check_24), + ONHOLD(2, R.string.type_on_hold, R.drawable.ic_baseline_pause_24), + DROPPED(3, R.string.type_dropped, R.drawable.ic_baseline_close_24), + PLANTOWATCH(4, R.string.type_plan_to_watch, R.drawable.ic_baseline_close_24), + NONE(5, R.string.type_none, R.drawable.ic_baseline_remove_red_eye_24); + + companion object { + fun fromInternalId(id: Int?) = values().find { value -> value.internalId == id } ?: NONE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index fdf4c467..6cc2b0c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -30,7 +30,6 @@ import android.widget.Toast import android.widget.Toast.LENGTH_SHORT import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager @@ -42,7 +41,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C.TIME_UNSET -import com.google.android.exoplayer2.ext.cast.CastPlayer import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.ui.AspectRatioFrameLayout @@ -51,14 +49,9 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.Util -import com.google.android.gms.cast.MediaInfo -import com.google.android.gms.cast.MediaMetadata -import com.google.android.gms.cast.MediaQueueItem -import com.google.android.gms.cast.MediaStatus 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.gms.common.images.WebImage import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.MainActivity.Companion.isInPIPMode import com.lagradost.cloudstream3.MainActivity.Companion.isInPlayer @@ -81,20 +74,16 @@ import com.lagradost.cloudstream3.ui.result.ResultViewModel import com.lagradost.cloudstream3.utils.CastHelper.startCast import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.saveViewPos +import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getId import kotlinx.android.synthetic.main.fragment_player.* import kotlinx.android.synthetic.main.player_custom_layout.* import kotlinx.coroutines.* -import org.json.JSONArray -import org.json.JSONObject import java.io.File import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession -import kotlin.concurrent.thread import kotlin.math.abs import kotlin.math.ceil import kotlin.properties.Delegates @@ -562,7 +551,7 @@ class PlayerFragment : Fragment() { private fun savePos() { if (this::exoPlayer.isInitialized) { if (exoPlayer.duration > 0 && exoPlayer.currentPosition > 0) { - context?.saveViewPos(getEpisode()?.id, exoPlayer.currentPosition, exoPlayer.duration) + context?.setViewPos(getEpisode()?.id, exoPlayer.currentPosition, exoPlayer.duration) } } } 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 d0115488..4591a5ad 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 @@ -28,8 +28,11 @@ import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable +import com.lagradost.cloudstream3.UIHelper.popupMenu +import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.player.PlayerData import com.lagradost.cloudstream3.ui.player.PlayerFragment import com.lagradost.cloudstream3.utils.CastHelper.startCast @@ -184,10 +187,10 @@ class ResultFragment : Fragment() { dialog.setOnDismissListener { currentLoadingCount++ } - // Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show() + // Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show() viewModel.loadEpisode(episodeClick.data, true) { data -> - if(currentLoadingCount != currentLoad) return@loadEpisode + if (currentLoadingCount != currentLoad) return@loadEpisode dialog.dismiss() when (data) { is Resource.Failure -> { @@ -238,6 +241,23 @@ class ResultFragment : Fragment() { result_episodes.adapter = adapter result_episodes.layoutManager = GridLayoutManager(context, 1) + result_bookmark_button.setOnClickListener { + it.popupMenuNoIcons( + items = WatchType.values() + .map { watchType -> Pair(watchType.internalId, watchType.stringRes) }, + //.map { watchType -> Triple(watchType.internalId, watchType.iconRes, watchType.stringRes) }, + ) { + context?.let { localContext -> + viewModel.updateWatchStatus(localContext, WatchType.fromInternalId(this.itemId)) + } + } + } + + observe(viewModel.watchStatus) { + //result_bookmark_button.setIconResource(it.iconRes) + result_bookmark_button.text = getString(it.stringRes) + } + observe(viewModel.allEpisodes) { allEpisodes = it } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index 811dc9f6..977885de 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -9,7 +9,10 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.ui.WatchType +import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.launch @@ -20,6 +23,24 @@ class ResultViewModel : ViewModel() { val episodes: LiveData> get() = _episodes private val dubStatus: MutableLiveData = MutableLiveData() + private val page: MutableLiveData = MutableLiveData() + private val id: MutableLiveData = MutableLiveData() + + private val _watchStatus: MutableLiveData = MutableLiveData() + val watchStatus: LiveData get() = _watchStatus + + fun updateWatchStatus(context: Context, status: WatchType) { + val currentId = id.value ?: return + _watchStatus.postValue(status) + context.setResultWatchState(currentId, status.internalId) + } + + private fun loadWatchStatus(context: Context, localId: Int? = null) { + val currentId = localId ?: id.value ?: return + val currentWatch = context.getResultWatchState(currentId) + _watchStatus.postValue(currentWatch) + } + fun reloadEpisodes(context: Context) { val current = _episodes.value ?: return val copy = current.map { @@ -29,10 +50,16 @@ class ResultViewModel : ViewModel() { _episodes.postValue(copy) } + // THIS SHOULD AT LEAST CLEAN IT UP, SO APIS CAN SWITCH DOMAIN + private fun getId(url: String, api: MainAPI): Int { + return url.replace(api.mainUrl, "").hashCode() + } + fun load(context: Context, url: String, apiName: String) = viewModelScope.launch { _apiName.postValue(apiName) + val api = getApiFromName(apiName) val data = safeApiCall { - getApiFromName(apiName).load(url) + api.load(url) } _resultResponse.postValue(data) @@ -40,6 +67,11 @@ class ResultViewModel : ViewModel() { is Resource.Success -> { val d = data.value if (d is LoadResponse) { + page.postValue(d) + val mainId = getId(d.url, api) + id.postValue(mainId) + loadWatchStatus(context, mainId) + when (d) { is AnimeLoadResponse -> { val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0 @@ -57,14 +89,14 @@ class ResultViewModel : ViewModel() { null, // TODO FIX SEASON i, apiName, - (d.url + index).hashCode(), + (mainId + index + 1), index, )) } _episodes.postValue(episodes) } - } + is TvSeriesLoadResponse -> { val episodes = ArrayList() for ((index, i) in d.episodes.withIndex()) { @@ -75,7 +107,7 @@ class ResultViewModel : ViewModel() { null, // TODO FIX SEASON i, apiName, - (d.url + index).hashCode(), + (mainId + index + 1).hashCode(), index, )) } @@ -88,7 +120,7 @@ class ResultViewModel : ViewModel() { 0, null, d.movieUrl, d.apiName, - (d.url).hashCode(), + (mainId + 1), 0, ))) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index 3f3e2afb..6b4dba2a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.utils -import android.app.Activity import android.content.Context import android.net.Uri import com.fasterxml.jackson.databind.DeserializationFeature @@ -21,10 +20,8 @@ import com.lagradost.cloudstream3.ui.MetadataHolder import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.main import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext import org.json.JSONObject -import kotlin.concurrent.thread object CastHelper { private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule()) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index f6642700..8bd1a23c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,22 +1,33 @@ package com.lagradost.cloudstream3.utils import android.content.Context +import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey const val VIDEO_POS_DUR = "video_pos_dur" +const val RESULT_WATCH_STATE = "result_watch_state" data class PosDur(val position: Long, val duration: Long) object DataStoreHelper { var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION - fun Context.saveViewPos(id: Int?, pos: Long, dur: Long) { - if(id == null) return + fun Context.setViewPos(id: Int?, pos: Long, dur: Long) { + if (id == null) return setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur)) } fun Context.getViewPos(id: Int): PosDur? { - return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) + return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) + } + + fun Context.setResultWatchState(id: Int?, status: Int) { + if (id == null) return + setKey("$currentAccount/$RESULT_WATCH_STATE", id.toString(), status) + } + + fun Context.getResultWatchState(id: Int): WatchType { + return WatchType.fromInternalId(getKey("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null)) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_check_24.xml b/app/src/main/res/drawable/ic_baseline_check_24.xml new file mode 100644 index 00000000..2501e9fd --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_close_24.xml b/app/src/main/res/drawable/ic_baseline_close_24.xml new file mode 100644 index 00000000..70db409b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_close_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_pause_24.xml b/app/src/main/res/drawable/ic_baseline_pause_24.xml new file mode 100644 index 00000000..f701d6f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_pause_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_visibility_off_24.xml b/app/src/main/res/drawable/ic_baseline_visibility_off_24.xml new file mode 100644 index 00000000..32ab2aed --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_visibility_off_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/material_outline_background.xml b/app/src/main/res/drawable/material_outline_background.xml new file mode 100644 index 00000000..a9d66572 --- /dev/null +++ b/app/src/main/res/drawable/material_outline_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 56eecfa9..5fee3311 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -61,14 +61,22 @@ --> - + + + + + - + 16dp 16dp - 4dp + 1dp 0dp 2dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcad170e..89d374ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,5 +22,12 @@ Share Open In Browser Skip Loading - Loading... + Loading… + + Watching + On-Hold + Completed + Dropped + Plan to Watch + None \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 07ae4b14..21a10220 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -32,6 +32,7 @@ @style/CustomCastExpandedController @style/CustomCastMiniController + @color/textColor @@ -118,6 +119,9 @@ @color/transparent +