fix: handle recordings not yet loaded in event details VM

This commit is contained in:
Felix 2020-04-08 19:44:26 +02:00
parent c0b32e560a
commit 795647c240
7 changed files with 91 additions and 60 deletions

View file

@ -23,13 +23,13 @@ import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem
import de.nicidienase.chaosflix.common.util.ConferenceUtil import de.nicidienase.chaosflix.common.util.ConferenceUtil
import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.LiveEvent
import de.nicidienase.chaosflix.common.util.SingleLiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent
import java.io.IOException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import retrofit2.Response import retrofit2.Response
import java.io.IOException
class MediaRepository( class MediaRepository(
private val recordingApi: RecordingService, private val recordingApi: RecordingService,
@ -101,25 +101,19 @@ class MediaRepository(
} }
} }
fun updateRecordingsForEvent(event: Event): LiveData<LiveEvent<State, List<Recording>, String>> { suspend fun updateRecordingsForEvent(event: Event): List<Recording>? {
val updateState = SingleLiveEvent<LiveEvent<State, List<Recording>, String>>() return try {
coroutineScope.launch(Dispatchers.IO) { val eventDto = recordingApi.getEventByGUIDSuspending(event.guid)
try { val recordingDtos = eventDto?.recordings
val eventDto = recordingApi.getEventByGUIDSuspending(event.guid) if (recordingDtos != null) {
val recordingDtos = eventDto?.recordings saveRecordings(event, recordingDtos)
if (recordingDtos != null) { } else {
val recordings: List<Recording> = saveRecordings(event, recordingDtos) null
updateState.postValue(LiveEvent(State.DONE, data = recordings)) }
} else { } catch (e: Exception) {
updateState.postValue(LiveEvent(State.DONE, error = "Error updating Recordings for ${event.title}")) e.printStackTrace()
} null
} catch (e: IOException) {
updateState.postValue(LiveEvent(State.DONE, error = e.message))
} catch (e: Exception) {
updateState.postValue(LiveEvent(State.DONE, error = "Error updating Recordings for ${event.title} (${e.cause})"))
e.printStackTrace() }
} }
return updateState
} }
suspend fun updateSingleEvent(guid: String): Event? = withContext(Dispatchers.IO) { suspend fun updateSingleEvent(guid: String): Event? = withContext(Dispatchers.IO) {

View file

@ -77,7 +77,8 @@ abstract class EventDao : BaseDao<Event>() {
@Query("""SELECT event.*, conference.acronym as conference @Query("""SELECT event.*, conference.acronym as conference
FROM event JOIN conference ON event.conferenceId=conference.id FROM event JOIN conference ON event.conferenceId=conference.id
WHERE conference.id = :confernceId""") WHERE conference.id = :confernceId
ORDER BY event.title""")
abstract fun getEventsWithConferenceForConfernce(confernceId: Long): LiveData<List<Event>> abstract fun getEventsWithConferenceForConfernce(confernceId: Long): LiveData<List<Event>>
override suspend fun updateOrInsertInternal(item: Event) { override suspend fun updateOrInsertInternal(item: Event) {

View file

@ -5,14 +5,14 @@ import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import de.nicidienase.chaosflix.BuildConfig import de.nicidienase.chaosflix.BuildConfig
import de.nicidienase.chaosflix.common.SingletonHolder2 import de.nicidienase.chaosflix.common.SingletonHolder2
import java.io.File
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
import okhttp3.Cache import okhttp3.Cache
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
class ApiFactory private constructor(private val recordingUrl: String, cache: File? = null) { class ApiFactory private constructor(private val recordingUrl: String, cache: File? = null) {
@ -52,6 +52,7 @@ class ApiFactory private constructor(private val recordingUrl: String, cache: Fi
return@Interceptor chain.proceed(requestWithUseragent) return@Interceptor chain.proceed(requestWithUseragent)
} catch (ex: SocketTimeoutException) { } catch (ex: SocketTimeoutException) {
Log.e("UserAgentIntercepor", ex.message, ex) Log.e("UserAgentIntercepor", ex.message, ex)
return@Interceptor null return@Interceptor null
} }
} }

View file

@ -15,10 +15,10 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.
import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem
import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.LiveEvent
import de.nicidienase.chaosflix.common.util.SingleLiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent
import java.io.File
import java.util.ArrayList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
import java.util.*
class DetailsViewModel( class DetailsViewModel(
private val database: ChaosflixDatabase, private val database: ChaosflixDatabase,
@ -30,16 +30,33 @@ class DetailsViewModel(
val state: SingleLiveEvent<LiveEvent<State, Bundle, String>> = val state: SingleLiveEvent<LiveEvent<State, Bundle, String>> =
SingleLiveEvent() SingleLiveEvent()
private var waitingForRecordings = false
val autoselectRecording: Boolean val autoselectRecording: Boolean
get() = preferencesManager.getAutoselectRecording() get() = preferencesManager.getAutoselectRecording()
fun setEvent(event: Event): LiveData<Event?> { fun setEvent(event: Event): LiveData<Event?> {
mediaRepository.updateRecordingsForEvent(event) viewModelScope.launch {
val recordings = mediaRepository.updateRecordingsForEvent(event)
if(waitingForRecordings){
if(recordings != null){
waitingForRecordings = false
val bundle = Bundle()
bundle.putParcelable(EVENT, event)
bundle.putParcelableArrayList(KEY_SELECT_RECORDINGS, ArrayList(recordings))
state.postValue(LiveEvent(State.SelectRecording, data = bundle))
} else {
state.postValue(LiveEvent(State.Error, error = "Could not load recordings."))
}
}
}
return database.eventDao().findEventByGuid(event.guid) return database.eventDao().findEventByGuid(event.guid)
} }
fun getRecordingForEvent(event: Event): LiveData<List<Recording>> { fun getRecordingForEvent(event: Event): LiveData<List<Recording>> {
mediaRepository.updateRecordingsForEvent(event) viewModelScope.launch {
mediaRepository.updateRecordingsForEvent(event)
}
return database.recordingDao().findRecordingByEvent(event.id) return database.recordingDao().findRecordingByEvent(event.id)
} }
@ -104,10 +121,15 @@ class DetailsViewModel(
} else { } else {
// select quality then playEvent // select quality then playEvent
val items: List<Recording> = database.recordingDao().findRecordingByEventSync(event.id) val items: List<Recording> = database.recordingDao().findRecordingByEventSync(event.id)
val bundle = Bundle() if(items.isNotEmpty()){
bundle.putParcelable(EVENT, event) val bundle = Bundle()
bundle.putParcelableArrayList(KEY_SELECT_RECORDINGS, ArrayList(items)) bundle.putParcelable(EVENT, event)
state.postValue(LiveEvent(State.SelectRecording, data = bundle)) bundle.putParcelableArrayList(KEY_SELECT_RECORDINGS, ArrayList(items))
state.postValue(LiveEvent(State.SelectRecording, data = bundle))
} else {
state.postValue(LiveEvent(State.Loading))
waitingForRecordings = true
}
} }
} }
} }
@ -164,7 +186,8 @@ class DetailsViewModel(
DownloadRecording, DownloadRecording,
DisplayEvent, DisplayEvent,
PlayExternal, PlayExternal,
Error Error,
Loading
} }
companion object { companion object {

View file

@ -72,6 +72,7 @@ import kotlinx.coroutines.launch
class EventDetailsFragment : DetailsSupportFragment() { class EventDetailsFragment : DetailsSupportFragment() {
private var selectDialog: AlertDialog? = null private var selectDialog: AlertDialog? = null
private var loadingDialog: AlertDialog? = null
private val uiScope = CoroutineScope(Dispatchers.Main) private val uiScope = CoroutineScope(Dispatchers.Main)
@ -81,8 +82,6 @@ class EventDetailsFragment : DetailsSupportFragment() {
private var event: Event? = null private var event: Event? = null
private var room: Room? = null private var room: Room? = null
private var currentRecordings: List<Recording>? = null
private lateinit var rowsAdapter: ArrayObjectAdapter private lateinit var rowsAdapter: ArrayObjectAdapter
private var relatedEventsAdapter: ArrayObjectAdapter? = null private var relatedEventsAdapter: ArrayObjectAdapter? = null
@ -169,6 +168,8 @@ class EventDetailsFragment : DetailsSupportFragment() {
} }
} }
DetailsViewModel.State.SelectRecording -> { DetailsViewModel.State.SelectRecording -> {
loadingDialog?.dismiss()
loadingDialog = null
val event: Event? = state.data?.getParcelable(DetailsViewModel.EVENT) val event: Event? = state.data?.getParcelable(DetailsViewModel.EVENT)
val recordings: List<Recording>? = state.data?.getParcelableArrayList(DetailsViewModel.KEY_SELECT_RECORDINGS) val recordings: List<Recording>? = state.data?.getParcelableArrayList(DetailsViewModel.KEY_SELECT_RECORDINGS)
if (event != null && recordings != null && recordings.isNotEmpty()) { if (event != null && recordings != null && recordings.isNotEmpty()) {
@ -184,14 +185,26 @@ class EventDetailsFragment : DetailsSupportFragment() {
DetailsViewModel.State.Error -> { DetailsViewModel.State.Error -> {
showError(state.error) showError(state.error)
} }
else -> { DetailsViewModel.State.Loading -> {
// Download showLoadingDialog()
Log.e(TAG, "Case not relevant for leanback UI, this should not happen")
} }
DetailsViewModel.State.PlayOfflineItem -> irrelevantCase()
DetailsViewModel.State.DownloadRecording -> irrelevantCase()
DetailsViewModel.State.PlayExternal -> irrelevantCase()
} }
}) })
} }
fun showLoadingDialog() {
loadingDialog = AlertDialog.Builder(requireContext())
.setTitle("Loading Recordings")
.create().apply { show() }
}
private fun irrelevantCase() {
Log.e(TAG, "Case not relevant for leanback UI, this should not happen")
}
private fun showError(errorMessage: String?) { private fun showError(errorMessage: String?) {
if (errorMessage != null && errorMessage.isNotBlank()) { if (errorMessage != null && errorMessage.isNotBlank()) {
view?.let { view?.let {
@ -315,7 +328,7 @@ class EventDetailsFragment : DetailsSupportFragment() {
} }
override fun onLoadFailed(errorDrawable: Drawable?) { override fun onLoadFailed(errorDrawable: Drawable?) {
detailsOverview.setImageDrawable(ContextCompat.getDrawable(requireContext(), DEFAULT_DRAWABLE)) detailsOverview.imageDrawable = ContextCompat.getDrawable(requireContext(), DEFAULT_DRAWABLE)
} }
}) })
} }

View file

@ -30,11 +30,10 @@ import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.EventsFragment { class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.EventsFragment {
private val NUM_COLUMNS = 4
private val handler = Handler() private val handler = Handler()
private val rowsAdapter: ArrayObjectAdapter = private val rowsAdapter: ArrayObjectAdapter =
ArrayObjectAdapter(CardPresenter(R.style.EventGridCardStyle)) ArrayObjectAdapter(CardPresenter(R.style.EventGridCardStyle))
private lateinit var defaultBackground: Drawable private lateinit var defaultBackground: Drawable
private var metrics: DisplayMetrics? = null private var metrics: DisplayMetrics? = null
private var backgroundTimer: Timer? = null private var backgroundTimer: Timer? = null
@ -49,10 +48,8 @@ class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.E
defaultBackground = this defaultBackground = this
} }
val conference: Conference? = arguments?.getParcelable(EventsRowsBrowseFragment.CONFERENCE) val conference: Conference = arguments?.getParcelable(EventsRowsBrowseFragment.CONFERENCE)
if (conference == null) { ?: throw IllegalStateException("No conference passed")
throw IllegalStateException("No conference passed")
}
loadImage(conference.logoUrl, this::setBadgeDrawable) loadImage(conference.logoUrl, this::setBadgeDrawable)
title = conference.title title = conference.title
@ -91,16 +88,16 @@ class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.E
options.centerCrop() options.centerCrop()
Glide.with(this) Glide.with(this)
.load(url) .load(url)
.apply(options) .apply(options)
.into(object : SimpleTarget<Drawable>() { .into(object : SimpleTarget<Drawable>() {
override fun onResourceReady( override fun onResourceReady(
resource: Drawable, resource: Drawable,
transition: Transition<in Drawable>? transition: Transition<in Drawable>?
) { ) {
consumer.invoke(resource) consumer.invoke(resource)
} }
}) })
} }
protected fun updateBackground(uri: String) { protected fun updateBackground(uri: String) {
@ -120,10 +117,10 @@ class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.E
private inner class ItemViewSelectedListener : OnItemViewSelectedListener { private inner class ItemViewSelectedListener : OnItemViewSelectedListener {
override fun onItemSelected( override fun onItemSelected(
itemViewHolder: Presenter.ViewHolder, itemViewHolder: Presenter.ViewHolder,
item: Any, item: Any,
rowViewHolder: RowPresenter.ViewHolder, rowViewHolder: RowPresenter.ViewHolder,
row: Row row: Row
) { ) {
if (item is Event) { if (item is Event) {
try { try {
@ -157,6 +154,8 @@ class EventsGridBrowseFragment : VerticalGridSupportFragment(), EventsActivity.E
private const val BACKGROUND_UPDATE_DELAY = 300 private const val BACKGROUND_UPDATE_DELAY = 300
private const val CONFERENCE = "conference" private const val CONFERENCE = "conference"
private const val NUM_COLUMNS = 4
fun create(conference: Conference): EventsGridBrowseFragment { fun create(conference: Conference): EventsGridBrowseFragment {
return EventsGridBrowseFragment().apply { return EventsGridBrowseFragment().apply {
arguments = Bundle().apply { putParcelable(CONFERENCE, conference) } arguments = Bundle().apply { putParcelable(CONFERENCE, conference) }

View file

@ -34,7 +34,7 @@ class ConferenceGroupFragment : BrowseFragment() {
StaggeredGridLayoutManager(columnCount, StaggeredGridLayoutManager.VERTICAL) StaggeredGridLayoutManager(columnCount, StaggeredGridLayoutManager.VERTICAL)
} }
view.layoutManager = layoutManager view.layoutManager = layoutManager
val conferencesAdapter: ConferenceRecyclerViewAdapter = ConferenceRecyclerViewAdapter(listener) val conferencesAdapter = ConferenceRecyclerViewAdapter(listener)
conferencesAdapter.setHasStableIds(true) conferencesAdapter.setHasStableIds(true)
view.adapter = conferencesAdapter view.adapter = conferencesAdapter
val groupId = conferenceGroup?.id val groupId = conferenceGroup?.id
@ -45,7 +45,7 @@ class ConferenceGroupFragment : BrowseFragment() {
setLoadingOverlayVisibility(false) setLoadingOverlayVisibility(false)
} }
conferencesAdapter.conferences = conferenceList conferencesAdapter.conferences = conferenceList
val layoutState = arguments?.getParcelable<Parcelable>(LAYOUTMANAGER_STATE)?.let { arguments?.getParcelable<Parcelable>(LAYOUTMANAGER_STATE)?.let {
layoutManager?.onRestoreInstanceState(it) layoutManager?.onRestoreInstanceState(it)
} }
} }