From a467060486bfe554749091a367604f8b5aba4bd6 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Fri, 30 Jul 2021 23:03:46 +0200 Subject: [PATCH] fixed potential crash issues --- .../cloudstream3/ui/home/HomeViewModel.kt | 46 +++++- .../cloudstream3/ui/player/PlayerFragment.kt | 23 ++- .../cloudstream3/ui/result/EpisodeAdapter.kt | 2 +- .../cloudstream3/ui/result/ResultFragment.kt | 145 ++++++++++-------- .../cloudstream3/ui/result/ResultViewModel.kt | 5 + .../cloudstream3/ui/search/SearchFragment.kt | 23 ++- .../cloudstream3/utils/DataStoreHelper.kt | 8 + .../cloudstream3/utils/VideoDownloadHelper.kt | 4 +- 8 files changed, 166 insertions(+), 90 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index a775c0c1..51e4d7b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.home +import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -8,9 +9,16 @@ import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.HomePageResponse import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.ui.WatchType +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds +import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData +import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class HomeViewModel : ViewModel() { var repo: APIRepository? = null @@ -25,7 +33,43 @@ class HomeViewModel : ViewModel() { return APIRepository(apis.first { it.hasMainPage }) } - fun load(api : MainAPI?) = viewModelScope.launch { + private val availableWatchStatusTypes = MutableLiveData>>() + private val bookmarks = MutableLiveData>() + + fun loadStoredData(context: Context, preferredWatchStatus: WatchType?) = viewModelScope.launch { + val watchStatusIds = withContext(Dispatchers.IO) { + context.getAllWatchStateIds().map { id -> + Pair(id, context.getResultWatchState(id)) + } + } + val length = WatchType.values().size + val currentWatchTypes = HashSet() + + for (watch in watchStatusIds) { + currentWatchTypes.add(watch.second) + if (currentWatchTypes.size >= length) { + break + } + } + + if (currentWatchTypes.size <= 0) { + bookmarks.postValue(ArrayList()) + return@launch + } + + val watchStatus = preferredWatchStatus ?: currentWatchTypes.first() + availableWatchStatusTypes.postValue( + Pair( + watchStatus, + currentWatchTypes.sortedBy { it.internalId }.toList() + ) + ) + val list = withContext(Dispatchers.IO) { + watchStatusIds.map { context.getBookmarkedData(it.first) } + } + } + + fun load(api: MainAPI?) = viewModelScope.launch { repo = if (api?.hasMainPage == true) { APIRepository(api) } else { 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 9b23576d..2008151e 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 @@ -598,7 +598,7 @@ class PlayerFragment : Fragment() { } } - private lateinit var volumeObserver: SettingsContentObserver + private var volumeObserver: SettingsContentObserver? = null companion object { fun newInstance(data: PlayerData, startPos: Long? = null) = @@ -913,19 +913,18 @@ class PlayerFragment : Fragment() { resizeMode = requireContext().getKey(RESIZE_MODE_KEY, 0)!! playbackSpeed = requireContext().getKey(PLAYBACK_SPEED_KEY, 1f)!! - volumeObserver = SettingsContentObserver( - Handler( - Looper.getMainLooper() - ), requireActivity() - ) - - activity?.contentResolver - ?.registerContentObserver( - android.provider.Settings.System.CONTENT_URI, true, volumeObserver + activity?.let { + it.contentResolver?.registerContentObserver( + android.provider.Settings.System.CONTENT_URI, true, SettingsContentObserver( + Handler( + Looper.getMainLooper() + ), it + ) ) + } if (!isDownloadedFile) { - viewModel = ViewModelProvider(requireActivity()).get(ResultViewModel::class.java) + viewModel = ViewModelProvider(activity ?: this).get(ResultViewModel::class.java) observeDirectly(viewModel.episodes) { _episodes -> episodes = _episodes @@ -1347,7 +1346,7 @@ class PlayerFragment : Fragment() { outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying) outState.putInt(RESIZE_MODE_KEY, resizeMode) outState.putFloat(PLAYBACK_SPEED, playbackSpeed) - if(!isDownloadedFile) { + if (!isDownloadedFile) { outState.putString("data", mapper.writeValueAsString(playerData)) } super.onSaveInstanceState(outState) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index b22614c2..472ccf61 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -197,7 +197,7 @@ class EpisodeAdapter( downloadButton.setUpButton( downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null, VideoDownloadHelper.DownloadEpisodeCached( - card.name, card.poster, card.episode, card.season, card.id, 0, card.rating, card.descript + card.name, card.poster, card.episode, card.season, card.id, 0, card.rating, card.descript, System.currentTimeMillis(), ) ) { if (it.action == DOWNLOAD_ACTION_DOWNLOAD) { 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 edbed8d7..dd748e3f 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 @@ -65,7 +65,9 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.android.synthetic.main.fragment_result.* +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.withContext import java.io.File import java.util.* import kotlin.collections.ArrayList @@ -168,7 +170,7 @@ class ResultFragment : Fragment() { savedInstanceState: Bundle?, ): View? { viewModel = - ViewModelProvider(requireActivity()).get(ResultViewModel::class.java) + ViewModelProvider(activity ?: this).get(ResultViewModel::class.java) return inflater.inflate(R.layout.fragment_result, container, false) } @@ -270,15 +272,17 @@ class ResultFragment : Fragment() { } } - if (activity?.isCastApiAvailable() == true) { - CastButtonFactory.setUpMediaRouteButton(activity, media_route_button) - val castContext = CastContext.getSharedInstance(requireActivity().applicationContext) + activity?.let { + if (it.isCastApiAvailable()) { + CastButtonFactory.setUpMediaRouteButton(it, media_route_button) + val castContext = CastContext.getSharedInstance(it.applicationContext) - if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = VISIBLE - castContext.addCastStateListener { state -> - if (media_route_button != null) { - if (state == CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = GONE else { - if (media_route_button.visibility == GONE) media_route_button.visibility = VISIBLE + if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = VISIBLE + castContext.addCastStateListener { state -> + if (media_route_button != null) { + if (state == CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = GONE else { + if (media_route_button.visibility == GONE) media_route_button.visibility = VISIBLE + } } } } @@ -302,7 +306,7 @@ class ResultFragment : Fragment() { } result_back.setOnClickListener { - requireActivity().popCurrentPage() + activity?.popCurrentPage() } fun handleAction(episodeClick: EpisodeClickEvent): Job = main { @@ -432,7 +436,8 @@ class ResultFragment : Fragment() { currentType ?: return@let, currentHeaderName ?: return@let, currentPoster ?: return@let, - currentId ?: return@let + currentId ?: return@let, + System.currentTimeMillis(), ) ) @@ -451,7 +456,8 @@ class ResultFragment : Fragment() { epData.id, parentId, epData.rating, - epData.descript + epData.descript, + System.currentTimeMillis(), ) ) @@ -559,76 +565,78 @@ class ResultFragment : Fragment() { } ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - if (activity?.checkWrite() != true) { - activity?.requestRW() - if (activity?.checkWrite() == true) return@main - } - val data = currentLinks ?: return@main - val subs = currentSubs - - val outputDir = requireContext().cacheDir - val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) - var text = "#EXTM3U" - if (subs != null) { - for (sub in subs) { - text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.lang}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.lang}\",URI=\"${sub.url}\"" + activity?.let { act -> + if (!act.checkWrite()) { + act.requestRW() + if (act.checkWrite()) return@main } + val data = currentLinks ?: return@main + val subs = currentSubs + + val outputDir = requireContext().cacheDir + val outputFile = withContext(Dispatchers.IO) { + File.createTempFile("mirrorlist", ".m3u8", outputDir) + } + var text = "#EXTM3U" + if (subs != null) { + for (sub in subs) { + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.lang}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.lang}\",URI=\"${sub.url}\"" + } + } + for (link in data.sortedBy { -it.quality }) { + text += "\n#EXTINF:, ${link.name}\n${link.url}" + } + outputFile.writeText(text) + + + val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) + + vlcIntent.setPackage(VLC_PACKAGE) + vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION) + + vlcIntent.setDataAndType( + FileProvider.getUriForFile( + act, + act.applicationContext.packageName + ".provider", + outputFile + ), "video/*" + ) + + val startId = VLC_FROM_PROGRESS + + var position = startId + if (startId == VLC_FROM_START) { + position = 1 + } else if (startId == VLC_FROM_PROGRESS) { + position = 0 + } + + vlcIntent.putExtra("position", position) + + vlcIntent.component = VLC_COMPONENT + requireContext().setKey(VLC_LAST_ID_KEY, episodeClick.data.id) + act.startActivityForResult(vlcIntent, VLC_REQUEST_CODE) } - for (link in data.sortedBy { -it.quality }) { - text += "\n#EXTINF:, ${link.name}\n${link.url}" - } - outputFile.writeText(text) - - - val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) - - vlcIntent.setPackage(VLC_PACKAGE) - vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION) - vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION) - vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION) - - vlcIntent.setDataAndType( - FileProvider.getUriForFile( - requireActivity(), - requireActivity().applicationContext.packageName + ".provider", - outputFile - ), "video/*" - ) - - val startId = VLC_FROM_PROGRESS - - var position = startId - if (startId == VLC_FROM_START) { - position = 1 - } else if (startId == VLC_FROM_PROGRESS) { - position = 0 - } - - vlcIntent.putExtra("position", position) - - vlcIntent.component = VLC_COMPONENT - requireContext().setKey(VLC_LAST_ID_KEY, episodeClick.data.id) - activity?.startActivityForResult(vlcIntent, VLC_REQUEST_CODE) } ACTION_PLAY_EPISODE_IN_PLAYER -> { if (buildInPlayer) { - (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() - .setCustomAnimations( + (activity as AppCompatActivity?)?.supportFragmentManager?.beginTransaction() + ?.setCustomAnimations( R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit - ) - .add( + )?.add( R.id.homeRoot, PlayerFragment.newInstance( PlayerData(index, null, 0), episodeClick.data.getRealPosition() ) - ) - .commit() + )?.commit() } } @@ -924,7 +932,8 @@ class ResultFragment : Fragment() { localId, localId, d.rating, - d.plot + d.plot, + System.currentTimeMillis(), ) ) { downloadClickEvent -> if (downloadClickEvent.action == DOWNLOAD_ACTION_DOWNLOAD) { 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 29ec26ec..ecb3fb1d 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 @@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos @@ -55,10 +56,14 @@ class ResultViewModel : ViewModel() { context.setResultWatchState(currentId, status.internalId) val resultPage = page.value if (resultPage != null) { + val current = context.getBookmarkedData(currentId) + val currentTime = System.currentTimeMillis() context.setBookmarkedData( currentId, DataStoreHelper.BookmarkedData( currentId, + current?.bookmarkedTime ?: currentTime, + currentTime, resultPage.name, resultPage.url, resultPage.apiName, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 2aa0cbb2..33aab033 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -62,6 +62,11 @@ class SearchFragment : Fragment() { fixGrid() } + override fun onDestroyView() { + main_search?.clearFocus() + super.onDestroyView() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -104,7 +109,8 @@ class SearchFragment : Fragment() { } val edit = settingsManagerLocal.edit() - edit.putStringSet(getString(R.string.search_providers_list_key), + edit.putStringSet( + getString(R.string.search_providers_list_key), apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet() ) edit.apply() @@ -142,7 +148,7 @@ class SearchFragment : Fragment() { search_loading_bar.alpha = 0f } is Resource.Failure -> { - // Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show() + // Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show() searchExitIcon.alpha = 1f search_loading_bar.alpha = 0f } @@ -152,15 +158,18 @@ class SearchFragment : Fragment() { } } } - allApi.providersActive = requireActivity().getApiSettings() + activity?.let { + allApi.providersActive = it.getApiSettings() + } main_search.setOnQueryTextFocusChangeListener { searchView, b -> if (b) { // https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview searchView?.postDelayed({ - val imm: InputMethodManager? = - requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager? - imm?.showSoftInput(view.findFocus(), 0) + (activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager?)?.showSoftInput( + view.findFocus(), + 0 + ) }, 200) } } @@ -169,7 +178,7 @@ class SearchFragment : Fragment() { //searchViewModel.search("iron man") //(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro") /* - (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() + (activity as AppCompatActivity?)?.supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, 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 a29fdb91..cd9b1d31 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.setKey const val VIDEO_POS_DUR = "video_pos_dur" @@ -27,6 +28,8 @@ object DataStoreHelper { data class BookmarkedData( val parentId: Int, + val bookmarkedTime : Long, + val latestUpdatedTime : Long, override val name: String, override val url: String, override val apiName: String, @@ -37,6 +40,11 @@ object DataStoreHelper { var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION + fun Context.getAllWatchStateIds(): List { + val folder = "$currentAccount/$RESULT_WATCH_STATE" + return getKeys(folder).mapNotNull { it.removePrefix(folder).toIntOrNull() } + } + fun Context.setBookmarkedData(id: Int?, data: BookmarkedData) { if (id == null) return setKey("$currentAccount/$RESULT_WATCH_STATE", id.toString(), data) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt index 201d0091..5adda5b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt @@ -12,14 +12,16 @@ object VideoDownloadHelper { val parentId: Int, val rating: Int?, val descript: String?, + val cacheTime: Long, ) data class DownloadHeaderCached( val apiName: String, val url: String, - val type : TvType, + val type: TvType, val name: String, val poster: String?, val id: Int, + val cacheTime: Long, ) } \ No newline at end of file