store data stuff

This commit is contained in:
LagradOst 2021-06-16 01:25:58 +02:00
parent 1e01abf929
commit 881ee223c2
17 changed files with 201 additions and 37 deletions

View file

@ -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<Boolean>()
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<Triple<Int, Int, Int>>,
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<Pair<Int, Int>>,
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
}
}

View file

@ -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
}
}

View file

@ -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)
}
}
}

View file

@ -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
@ -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
}

View file

@ -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<List<ResultEpisode>> get() = _episodes
private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
private val page: MutableLiveData<LoadResponse> = MutableLiveData()
private val id: MutableLiveData<Int> = MutableLiveData()
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData()
val watchStatus: LiveData<WatchType> 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<ResultEpisode>()
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,
)))
}

View file

@ -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())

View file

@ -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) {
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<PosDur>("$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<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null))
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/bitDarkerGrayBackground"/>
<stroke android:width="@dimen/mtrl_btn_stroke_size" android:color="@color/mtrl_btn_stroke_color_selector"/>
</shape>

View file

@ -61,14 +61,22 @@
</LinearLayout>-->
<LinearLayout
android:layout_marginTop="10dp"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
app:cardCornerRadius="@dimen/roundedImageRadius"
android:layout_width="100dp"
android:layout_height="140dp">
<ImageView
android:id="@+id/result_poster"
android:layout_width="100dp" android:layout_height="150dp"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:src="@drawable/example_poster"
android:contentDescription="@string/result_poster"/>
</androidx.cardview.widget.CardView>
<LinearLayout android:layout_marginLeft="10dp"
android:gravity="center_vertical"

View file

@ -23,7 +23,7 @@
android:id="@+id/backgroundCard"
app:cardBackgroundColor="@color/darkBackground"
>
<!-- USING CROP RATIO (182/268), centerCrop for fill -->
<ImageView
android:duplicateParentState="true"
android:id="@+id/imageView"

View file

@ -2,7 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="roundedImageRadius">4dp</dimen>
<dimen name="roundedImageRadius">1dp</dimen>
<dimen name="navbarHeight">0dp</dimen>
<dimen name="card_corner_radius">2dp</dimen>
</resources>

View file

@ -22,5 +22,12 @@
<string name="result_share">Share</string>
<string name="result_open_in_browser">Open In Browser</string>
<string name="skip_loading">Skip Loading</string>
<string name="loading_chromecast">Loading...</string>
<string name="loading_chromecast">Loading…</string>
<string name="type_watching">Watching</string>
<string name="type_on_hold">On-Hold</string>
<string name="type_completed">Completed</string>
<string name="type_dropped">Dropped</string>
<string name="type_plan_to_watch">Plan to Watch</string>
<string name="type_none">None</string>
</resources>

View file

@ -32,6 +32,7 @@
@style/CustomCastExpandedController
</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<!--<item name="mediaRouteButtonTint">?attr/colorPrimary</item>-->
<!-- DEF STYLE -->
<item name="textColor">@color/textColor</item>
@ -118,6 +119,9 @@
<item name="android:windowBackground">@color/transparent</item>
</style>
<style name="PopupMenu" parent="@android:style/Widget.PopupMenu">
<item name="android:backgroundTint">?attr/bitDarkerGrayBackground</item>
</style>
<!-- CHROMECAST -->
<style name="CustomCastExpandedController" parent="CastExpandedController">