From f92cf0e31c1b268429f2f839e43035715bbcc521 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 29 Dec 2019 00:44:23 +0100 Subject: [PATCH 01/21] add activity to import events from Fahrplan-App --- common/build.gradle | 7 +- .../common/eventimport/FahrplanExport.kt | 6 ++ .../common/eventimport/FahrplanLecture.kt | 35 ++++++++ .../recording/persistence/EventDao.kt | 6 ++ .../common/mediadata/network/ApiFactory.kt | 9 ++ .../network/FahrplanMappingService.kt | 9 ++ .../mediadata/network/RecordingService.kt | 3 + .../common/mediadata/sync/Downloader.kt | 36 ++++---- .../viewmodel/FavoritesImportViewModel.kt | 76 ++++++++++++++++ .../common/viewmodel/ViewModelFactory.kt | 61 +++++++++---- touch/src/main/AndroidManifest.xml | 7 ++ .../touch/FavoritesImportActivity.kt | 20 +++++ .../touch/FavoritesImportFragment.kt | 90 +++++++++++++++++++ .../res/layout/activity_favorites_import.xml | 29 ++++++ .../res/layout/fragment_favorites_import.xml | 20 +++++ 15 files changed, 378 insertions(+), 36 deletions(-) create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/FahrplanMappingService.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt create mode 100644 touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportActivity.kt create mode 100644 touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt create mode 100644 touch/src/main/res/layout/activity_favorites_import.xml create mode 100644 touch/src/main/res/layout/fragment_favorites_import.xml diff --git a/common/build.gradle b/common/build.gradle index ff92e6b8..b04c6c76 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -103,8 +103,10 @@ dependencies { api 'androidx.appcompat:appcompat:1.1.0' - api "androidx.lifecycle:lifecycle-extensions:2.1.0" - api 'androidx.lifecycle:lifecycle-common-java8:2.1.0' + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" api 'androidx.recyclerview:recyclerview:1.1.0' @@ -116,6 +118,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:converter-gson:2.6.2' + api "com.google.code.gson:gson:2.8.6" api 'com.google.android.exoplayer:exoplayer:2.9.6' api 'com.github.bumptech.glide:glide:4.9.0' diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt new file mode 100644 index 00000000..08cdfe49 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt @@ -0,0 +1,6 @@ +package de.nicidienase.chaosflix.common.eventimport + +data class FahrplanExport( + val conference: String, + val lectures: List +) \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt new file mode 100644 index 00000000..de7ba99f --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt @@ -0,0 +1,35 @@ +package de.nicidienase.chaosflix.common.eventimport + +import com.google.gson.annotations.SerializedName + +data class FahrplanLecture( + var lectureId: String = "", + var title: String, + var subtitle: String = "", + var day: Int = 0, + var room: String? = null, + var slug: String? = null, + var url: String? = null, + var startTime: Int = 0, + var duration: Int = 0, + var speakers: String? = null, + var track: String? = null, + var type: String? = null, + var lang: String? = null, + @SerializedName("abstractt") + var abstract: String = "", + var description: String = "", + var relStartTime: Int = 0, + var links: String? = null, + var date: String? = null, + var dateUTC: Long = 0, + var roomIndex: Int = 0, + var recordingLicense: String? = null, + var recordingOptOut: Boolean = false +) { + + companion object { + val RECORDING_OPTOUT_ON = true + val RECORDING_OPTOUT_OFF = false + } +} diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt index 0ec0b8da..cba9d17d 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt @@ -55,6 +55,12 @@ abstract class EventDao : BaseDao() { @Query("DElETE FROM event") abstract fun delete() + @Query("SELECT * FROM event WHERE link = :url LIMIT 1") + abstract suspend fun findEventByFahrplanUrl(url: String): Event? + + @Query("SELECT * FROM event WHERE title LIKE :search LIMIT 1") + abstract suspend fun findEventByTitleSuspend(search: String): Event? + override fun updateOrInsertInternal(item: Event) { if (item.id != 0L) { update(item) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt index 82e7fefd..1c578b40 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt @@ -41,6 +41,15 @@ class ApiFactory private constructor(val res: Resources) { .build() .create(StreamingApi::class.java) } + val fahrplanMappingApi: FahrplanMappingService by lazy { + Retrofit.Builder() + .baseUrl("https://gist.githubusercontent.com") + .client(client) + .addConverterFactory(gsonConverterFactory) + .build() + .create(FahrplanMappingService::class.java) + } + private val useragentInterceptor: Interceptor = Interceptor { chain -> val requestWithUseragent = chain.request().newBuilder() .header("User-Agent", chaosflixUserAgent) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/FahrplanMappingService.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/FahrplanMappingService.kt new file mode 100644 index 00000000..fb1005b2 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/FahrplanMappingService.kt @@ -0,0 +1,9 @@ +package de.nicidienase.chaosflix.common.mediadata.network + +import retrofit2.http.GET + +interface FahrplanMappingService { + + @GET("NiciDieNase/d8bbb9f7b73efddd0cf6e6c4aa93f3ba/raw/02f8604bac4a9a150037eb82aeaf5bc7194d2682/fahrplan_mappings.json") + suspend fun getFahrplanMappings(): Map> +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt index 17ad1485..9a0e6c6d 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt @@ -21,6 +21,9 @@ interface RecordingService { @GET("public/conferences/{name}") fun getConferenceByName(@Path("name") name: String): Call + @GET("public/conferences/{name}") + suspend fun getConferenceByNameSuspending(@Path("name") name: String): ConferenceDto? + @GET("public/events/{id}") fun getEvent(@Path("id") id: Long): Call diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt index a86a21fe..f4ae43f8 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt @@ -2,7 +2,6 @@ package de.nicidienase.chaosflix.common.mediadata.sync import androidx.lifecycle.LiveData import de.nicidienase.chaosflix.common.ChaosflixDatabase -import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferenceDto import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferencesWrapper import de.nicidienase.chaosflix.common.mediadata.entities.recording.EventDto import de.nicidienase.chaosflix.common.mediadata.entities.recording.RecordingDto @@ -16,6 +15,10 @@ import de.nicidienase.chaosflix.common.util.ConferenceUtil import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent import de.nicidienase.chaosflix.common.util.ThreadHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import retrofit2.Response import java.io.IOException @@ -26,6 +29,9 @@ class Downloader( private val threadHandler = ThreadHandler() + private val supervisorJob = SupervisorJob() + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO + supervisorJob) + enum class DownloaderState { RUNNING, DONE } @@ -62,23 +68,13 @@ class Downloader( fun updateEventsForConference(conference: Conference): LiveData, String>> { val updateState = SingleLiveEvent, String>>() updateState.postValue(LiveEvent(DownloaderState.RUNNING)) - threadHandler.runOnBackgroundThread { - val response: Response? + coroutineScope.launch { try { - response = recordingApi.getConferenceByName(conference.acronym).execute() + val list = + updateEventsForConferencesSuspending(conference) + updateState.postValue(LiveEvent(DownloaderState.DONE, data = list)) } catch (e: IOException) { updateState.postValue(LiveEvent(DownloaderState.DONE, error = e.message)) - return@runOnBackgroundThread - } - if (!response.isSuccessful) { - updateState.postValue(LiveEvent(DownloaderState.DONE, error = response.message())) - return@runOnBackgroundThread - } - try { - val persistentEvents = response.body()?.events?.let { events -> - return@let saveEvents(conference, events) - } - updateState.postValue(LiveEvent(DownloaderState.DONE, data = persistentEvents)) } catch (e: Exception) { updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating Events for ${conference.acronym}")) e.printStackTrace() @@ -87,6 +83,16 @@ class Downloader( return updateState } + internal suspend fun updateEventsForConferencesSuspending(conference: Conference): List { + val conferenceByName = recordingApi.getConferenceByNameSuspending(conference.acronym) + val events = conferenceByName?.events + return if (events != null) { + saveEvents(conference, events) + } else { + emptyList() + } + } + fun updateRecordingsForEvent(event: Event): LiveData, String>> { val updateState = SingleLiveEvent, String>>() diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt new file mode 100644 index 00000000..dd6b0ed3 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -0,0 +1,76 @@ +package de.nicidienase.chaosflix.common.viewmodel + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.gson.Gson +import de.nicidienase.chaosflix.common.eventimport.FahrplanExport +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.ConferenceDao +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.EventDao +import de.nicidienase.chaosflix.common.mediadata.network.FahrplanMappingService +import de.nicidienase.chaosflix.common.mediadata.sync.Downloader +import de.nicidienase.chaosflix.common.util.LiveEvent +import de.nicidienase.chaosflix.common.util.SingleLiveEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class FavoritesImportViewModel( + private val conferenceDao: ConferenceDao, + private val eventDao: EventDao, + private val downloader: Downloader, + private val mappingService: FahrplanMappingService +) : ViewModel() { + + val state: SingleLiveEvent, Exception>> = SingleLiveEvent() + + fun handleUrls(urls: List) { + viewModelScope.launch { + val events = urls.mapNotNull { eventDao.findEventByFahrplanUrl(it) } + state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) + } + } + + fun handleLectures(string: String) { + state.postValue(LiveEvent(State.WORKING)) + viewModelScope.launch(Dispatchers.IO) { + val export = Gson().fromJson(string, FahrplanExport::class.java) + updateConferences(export.conference) + val events: List + try { + events = export.lectures.mapNotNull { eventDao.findEventByTitleSuspend(it.title) } + state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) + } catch (e: Exception) { + state.postValue(LiveEvent(State.ERROR, null, e)) + } + } + } + + private suspend fun updateConferences(conference: String) { + val fahrplanMappings = mappingService.getFahrplanMappings() + Log.d(TAG, "Updating conferences for $conference, mappings=$fahrplanMappings") + if (fahrplanMappings.containsKey(conference)) { + fahrplanMappings[conference]?.let { keys -> + for (conf in keys) { + conferenceDao.findConferenceByAcronymSync(conf)?.let { conf -> + val list = + downloader.updateEventsForConferencesSuspending(conf) + Log.d(TAG, "updated ${conf.acronym}, got ${list.size} events") + } + } + } + } else { + Log.d(TAG, "Did not update any conferences") + } + } + + enum class State { + WORKING, + EVENTS_FOUND, + ERROR + } + + companion object { + private val TAG = FavoritesImportViewModel::class.java.simpleName + } +} diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt index 590e8ef7..b13e22ed 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt @@ -1,11 +1,10 @@ package de.nicidienase.chaosflix.common.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import android.content.Context -import android.content.res.Resources import android.os.Environment import android.preference.PreferenceManager +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import de.nicidienase.chaosflix.common.ChaosflixDatabase import de.nicidienase.chaosflix.common.OfflineItemManager import de.nicidienase.chaosflix.common.PreferencesManager @@ -22,29 +21,53 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider private val recordingApi = apiFactory.recordingApi private val streamingApi = apiFactory.streamingApi private val preferencesManager = - PreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.applicationContext)) + PreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.applicationContext)) private val offlineItemManager = - OfflineItemManager(context.applicationContext, database.offlineEventDao(), preferencesManager) + OfflineItemManager( + context.applicationContext, + database.offlineEventDao(), + preferencesManager + ) private val downloader by lazy { Downloader(recordingApi, database) } private val externalFilesDir = Environment.getExternalStorageDirectory() private val resourcesFacade by lazy { ResourcesFacade(context) } @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(BrowseViewModel::class.java)) { - return BrowseViewModel(offlineItemManager, database, recordingApi, streamingApi, preferencesManager, resourcesFacade) as T - } else if (modelClass.isAssignableFrom(PlayerViewModel::class.java)) { - return PlayerViewModel(database) as T - } else if (modelClass.isAssignableFrom(DetailsViewModel::class.java)) { - return DetailsViewModel(database, offlineItemManager, preferencesManager, downloader) as T - } else if (modelClass.isAssignableFrom(PreferencesViewModel::class.java)) { - return PreferencesViewModel(downloader, database.watchlistItemDao(), externalFilesDir) as T - } else { - throw UnsupportedOperationException("The requested ViewModel is currently unsupported. " + - "Please make sure to implement are correct creation of it. " + - " Request: ${modelClass.canonicalName}") + return when (modelClass) { + BrowseViewModel::class.java -> BrowseViewModel( + offlineItemManager, + database, + recordingApi, + streamingApi, + preferencesManager, + resourcesFacade + ) as T + PlayerViewModel::class.java -> PlayerViewModel(database) as T + DetailsViewModel::class.java -> DetailsViewModel( + database, + offlineItemManager, + preferencesManager, + downloader + ) as T + PreferencesViewModel::class.java -> PreferencesViewModel( + downloader, + database.watchlistItemDao(), + externalFilesDir + ) as T + FavoritesImportViewModel::class.java -> FavoritesImportViewModel( + database.conferenceDao(), + database.eventDao(), + downloader, + apiFactory.fahrplanMappingApi + ) as T + else -> throw UnsupportedOperationException( + "The requested ViewModel is currently unsupported. " + + "Please make sure to implement are correct creation of it. " + + " Request: ${modelClass.canonicalName}" + ) } } - companion object : SingletonHolder(::ViewModelFactory) - } \ No newline at end of file + companion object : SingletonHolder(::ViewModelFactory) +} \ No newline at end of file diff --git a/touch/src/main/AndroidManifest.xml b/touch/src/main/AndroidManifest.xml index 87cc5f60..61e10bdc 100644 --- a/touch/src/main/AndroidManifest.xml +++ b/touch/src/main/AndroidManifest.xml @@ -59,6 +59,13 @@ android:parentActivityName=".browse.BrowseActivity"/> + + + + + + + (R.id.toolbar) + setSupportActionBar(toolbar) + supportActionBar?.title = "Import" + } + + companion object { + private val TAG = FavoritesImportActivity::class.java.simpleName + } +} \ No newline at end of file diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt new file mode 100644 index 00000000..528da11e --- /dev/null +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt @@ -0,0 +1,90 @@ +package de.nicidienase.chaosflix.touch + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel +import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory +import de.nicidienase.chaosflix.touch.browse.adapters.EventRecyclerViewAdapter +import de.nicidienase.chaosflix.touch.databinding.FragmentFavoritesImportBinding + +class FavoritesImportFragment : Fragment() { + + private lateinit var viewModel: FavoritesImportViewModel + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) + + viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) + + binding.importList.layoutManager = LinearLayoutManager(context) + val eventRecyclerViewAdapter = EventRecyclerViewAdapter { + } + binding.importList.adapter = eventRecyclerViewAdapter + + viewModel.state.observe(this, Observer { + when (it.state) { + FavoritesImportViewModel.State.EVENTS_FOUND -> { + it.data?.let { events -> + eventRecyclerViewAdapter.items = events + eventRecyclerViewAdapter.notifyDataSetChanged() + } + binding.incOverlay.loadingOverlay.visibility = View.GONE + } + FavoritesImportViewModel.State.WORKING -> { + binding.incOverlay.loadingOverlay.visibility = View.VISIBLE + } + FavoritesImportViewModel.State.ERROR -> { + Log.e(TAG, "Error", it.error) + } + } + }) + + val intent = activity?.intent + when { + intent?.action == Intent.ACTION_SEND -> { + when (intent.type) { + "text/plain" -> handleSendText(intent) + "text/json" -> handleJson(intent) + } + } + else -> { + // Handle other intents, such as being started from the home screen + } + } + + return binding.root + } + + private fun handleSendText(intent: Intent) { + val extra = intent.getStringExtra(Intent.EXTRA_TEXT) + Log.d(TAG, extra) + if (extra != null) { + val urls = extra.split("\n").filter { it.startsWith("http") } + Log.d(TAG, "Urls: $urls") + viewModel.handleUrls(urls) + } + } + + private fun handleJson(intent: Intent) { + val extra = intent.getStringExtra(Intent.EXTRA_TEXT) + if (extra.isNotEmpty()) { + viewModel.handleLectures(extra) + } + } + + companion object { + private val TAG = FavoritesImportFragment::class.java.simpleName + } +} \ No newline at end of file diff --git a/touch/src/main/res/layout/activity_favorites_import.xml b/touch/src/main/res/layout/activity_favorites_import.xml new file mode 100644 index 00000000..836f47fc --- /dev/null +++ b/touch/src/main/res/layout/activity_favorites_import.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/touch/src/main/res/layout/fragment_favorites_import.xml b/touch/src/main/res/layout/fragment_favorites_import.xml new file mode 100644 index 00000000..3078938c --- /dev/null +++ b/touch/src/main/res/layout/fragment_favorites_import.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file From cca71e499d06c235c40b2bdc50d4766cfef0b2a1 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 29 Dec 2019 20:32:37 +0100 Subject: [PATCH 02/21] display an import items shared from Fahrplan-App --- .../chaosflix/common/ImportItem.kt | 10 ++ .../recording/persistence/EventDao.kt | 3 + .../entities/watchlist/WatchlistItemDao.kt | 3 + .../viewmodel/FavoritesImportViewModel.kt | 39 +++-- .../common/viewmodel/ViewModelFactory.kt | 1 + touch/src/main/AndroidManifest.xml | 2 +- .../chaosflix/touch/BindingAdapters.kt | 3 +- .../browse/adapters/ImportItemAdapter.kt | 33 +++++ .../adapters/ItemRecyclerViewAdapter.kt | 4 +- .../FavoritesImportActivity.kt | 3 +- .../FavoritesImportFragment.kt | 53 ++++--- .../res/layout/activity_favorites_import.xml | 2 +- .../res/layout/fragment_favorites_import.xml | 29 +++- .../main/res/layout/item_favorit_import.xml | 133 ++++++++++++++++++ touch/src/main/res/layout/loading_overlay.xml | 1 + 15 files changed, 270 insertions(+), 49 deletions(-) create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt create mode 100644 touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt rename touch/src/main/java/de/nicidienase/chaosflix/touch/{ => favoritesimport}/FavoritesImportActivity.kt (85%) rename touch/src/main/java/de/nicidienase/chaosflix/touch/{ => favoritesimport}/FavoritesImportFragment.kt (73%) create mode 100644 touch/src/main/res/layout/item_favorit_import.xml diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt b/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt new file mode 100644 index 00000000..4260b966 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt @@ -0,0 +1,10 @@ +package de.nicidienase.chaosflix.common + +import de.nicidienase.chaosflix.common.eventimport.FahrplanLecture +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event + +data class ImportItem( + val lecture: FahrplanLecture, + var event: Event?, + var selected: Boolean = false +) \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt index cba9d17d..0fb02d7e 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt @@ -61,6 +61,9 @@ abstract class EventDao : BaseDao() { @Query("SELECT * FROM event WHERE title LIKE :search LIMIT 1") abstract suspend fun findEventByTitleSuspend(search: String): Event? + @Query("SELECT * FROM event WHERE title LIKE :title LIMIT 1") + abstract fun findSingleEventByTitle(title: String): LiveData + override fun updateOrInsertInternal(item: Event) { if (item.id != 0L) { update(item) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt index af2ee3c4..b4d29914 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt @@ -26,4 +26,7 @@ interface WatchlistItemDao { @Query("DELETE from watchlist_item WHERE event_guid = :guid") fun deleteItem(guid: String) + + @Query("SELECT * from watchlist_item WHERE event_guid = :guid LIMIT 1") + suspend fun getItemForGuid(guid: String): WatchlistItem? } \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index dd6b0ed3..2198c0ae 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -4,12 +4,14 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.gson.Gson +import de.nicidienase.chaosflix.common.ImportItem import de.nicidienase.chaosflix.common.eventimport.FahrplanExport import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.ConferenceDao -import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.EventDao import de.nicidienase.chaosflix.common.mediadata.network.FahrplanMappingService import de.nicidienase.chaosflix.common.mediadata.sync.Downloader +import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem +import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItemDao import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent import kotlinx.coroutines.Dispatchers @@ -18,27 +20,23 @@ import kotlinx.coroutines.launch class FavoritesImportViewModel( private val conferenceDao: ConferenceDao, private val eventDao: EventDao, + private val watchlistItemDao: WatchlistItemDao, private val downloader: Downloader, private val mappingService: FahrplanMappingService ) : ViewModel() { - val state: SingleLiveEvent, Exception>> = SingleLiveEvent() - - fun handleUrls(urls: List) { - viewModelScope.launch { - val events = urls.mapNotNull { eventDao.findEventByFahrplanUrl(it) } - state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) - } - } + val state: SingleLiveEvent, Exception>> = SingleLiveEvent() fun handleLectures(string: String) { state.postValue(LiveEvent(State.WORKING)) viewModelScope.launch(Dispatchers.IO) { val export = Gson().fromJson(string, FahrplanExport::class.java) - updateConferences(export.conference) - val events: List + val events: List try { - events = export.lectures.mapNotNull { eventDao.findEventByTitleSuspend(it.title) } + updateConferences(export.conference) + events = export.lectures.mapNotNull { ImportItem( + lecture = it, + event = eventDao.findEventByTitleSuspend(it.title)) } state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) } catch (e: Exception) { state.postValue(LiveEvent(State.ERROR, null, e)) @@ -46,6 +44,22 @@ class FavoritesImportViewModel( } } + fun import(events: List) { + state.postValue(LiveEvent(State.WORKING)) + viewModelScope.launch(Dispatchers.IO) { + for (item in events) { + Log.d(TAG, "${item.lecture.title}: ${item.selected}") + val guid = item.event?.guid + if (item.selected && + guid != null && + watchlistItemDao.getItemForGuid(guid) == null) { + watchlistItemDao.saveItem(WatchlistItem(eventGuid = guid)) + } + } + state.postValue(LiveEvent(State.IMPORT_DONE)) + } + } + private suspend fun updateConferences(conference: String) { val fahrplanMappings = mappingService.getFahrplanMappings() Log.d(TAG, "Updating conferences for $conference, mappings=$fahrplanMappings") @@ -67,6 +81,7 @@ class FavoritesImportViewModel( enum class State { WORKING, EVENTS_FOUND, + IMPORT_DONE, ERROR } diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt index b13e22ed..3cde9d5d 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt @@ -58,6 +58,7 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider FavoritesImportViewModel::class.java -> FavoritesImportViewModel( database.conferenceDao(), database.eventDao(), + database.watchlistItemDao(), downloader, apiFactory.fahrplanMappingApi ) as T diff --git a/touch/src/main/AndroidManifest.xml b/touch/src/main/AndroidManifest.xml index 61e10bdc..ec23730c 100644 --- a/touch/src/main/AndroidManifest.xml +++ b/touch/src/main/AndroidManifest.xml @@ -59,7 +59,7 @@ android:parentActivityName=".browse.BrowseActivity"/> - + diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/BindingAdapters.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/BindingAdapters.kt index 4e269c40..2925ccb8 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/BindingAdapters.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/BindingAdapters.kt @@ -7,7 +7,8 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions @BindingAdapter("bind:imageUrl") -fun loadImage(imageView: ImageView, url: String) { +fun loadImage(imageView: ImageView, url: String?) { + if (url == null) return Glide.with(imageView.context) .load(url) .apply(RequestOptions().fitCenter()) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt new file mode 100644 index 00000000..d72e1cbd --- /dev/null +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -0,0 +1,33 @@ +package de.nicidienase.chaosflix.touch.browse.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding +import de.nicidienase.chaosflix.common.ImportItem + +class ImportItemAdapter(private val clickListener: ((ImportItem) -> Unit)? = null) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem === newItem + override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem == newItem +}) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemFavoritImportBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = getItem(position) + holder.binding.item = item + holder.binding.importItemEvent.setOnClickListener { + holder.binding.checkBox.apply { + isChecked = !isChecked + } + } + } + + class ViewHolder(val binding: ItemFavoritImportBinding) : RecyclerView.ViewHolder(binding.root) +} \ No newline at end of file diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ItemRecyclerViewAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ItemRecyclerViewAdapter.kt index 5a4cbad3..cad4f9a4 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ItemRecyclerViewAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ItemRecyclerViewAdapter.kt @@ -5,8 +5,8 @@ import android.widget.Filter import android.widget.Filterable import java.util.Collections -abstract class ItemRecyclerViewAdapter() : - androidx.recyclerview.widget.RecyclerView.Adapter(), Filterable { +abstract class ItemRecyclerViewAdapter : + RecyclerView.Adapter(), Filterable { abstract fun getComparator(): Comparator? diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt similarity index 85% rename from touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportActivity.kt rename to touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt index ebb2f589..ddad8d4d 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt @@ -1,8 +1,9 @@ -package de.nicidienase.chaosflix.touch +package de.nicidienase.chaosflix.touch.favoritesimport import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import de.nicidienase.chaosflix.touch.R class FavoritesImportActivity : AppCompatActivity() { diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt similarity index 73% rename from touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt rename to touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 528da11e..71510dd0 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -1,4 +1,4 @@ -package de.nicidienase.chaosflix.touch +package de.nicidienase.chaosflix.touch.favoritesimport import android.content.Intent import android.os.Bundle @@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory -import de.nicidienase.chaosflix.touch.browse.adapters.EventRecyclerViewAdapter +import de.nicidienase.chaosflix.touch.browse.adapters.ImportItemAdapter import de.nicidienase.chaosflix.touch.databinding.FragmentFavoritesImportBinding class FavoritesImportFragment : Fragment() { @@ -25,20 +25,23 @@ class FavoritesImportFragment : Fragment() { savedInstanceState: Bundle? ): View? { val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) - + binding.setLifecycleOwner(this) viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) binding.importList.layoutManager = LinearLayoutManager(context) - val eventRecyclerViewAdapter = EventRecyclerViewAdapter { + val adapter = ImportItemAdapter() + binding.importList.adapter = adapter + + binding.appCompatButton.setOnClickListener { + viewModel.import(adapter.currentList) } - binding.importList.adapter = eventRecyclerViewAdapter viewModel.state.observe(this, Observer { when (it.state) { FavoritesImportViewModel.State.EVENTS_FOUND -> { it.data?.let { events -> - eventRecyclerViewAdapter.items = events - eventRecyclerViewAdapter.notifyDataSetChanged() + adapter.submitList(events) + adapter.notifyDataSetChanged() } binding.incOverlay.loadingOverlay.visibility = View.GONE } @@ -48,32 +51,28 @@ class FavoritesImportFragment : Fragment() { FavoritesImportViewModel.State.ERROR -> { Log.e(TAG, "Error", it.error) } - } - }) - - val intent = activity?.intent - when { - intent?.action == Intent.ACTION_SEND -> { - when (intent.type) { - "text/plain" -> handleSendText(intent) - "text/json" -> handleJson(intent) + FavoritesImportViewModel.State.IMPORT_DONE -> { + // TODO navigate to favorites + activity?.finish() } } - else -> { - // Handle other intents, such as being started from the home screen - } - } + }) return binding.root } - private fun handleSendText(intent: Intent) { - val extra = intent.getStringExtra(Intent.EXTRA_TEXT) - Log.d(TAG, extra) - if (extra != null) { - val urls = extra.split("\n").filter { it.startsWith("http") } - Log.d(TAG, "Urls: $urls") - viewModel.handleUrls(urls) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val intent = activity?.intent + when { + intent?.action == Intent.ACTION_SEND -> { + when (intent.type) { + "text/json" -> handleJson(intent) + } + } + else -> { + // Handle other intents, such as being started from the home screen + } } } diff --git a/touch/src/main/res/layout/activity_favorites_import.xml b/touch/src/main/res/layout/activity_favorites_import.xml index 836f47fc..120cb5c9 100644 --- a/touch/src/main/res/layout/activity_favorites_import.xml +++ b/touch/src/main/res/layout/activity_favorites_import.xml @@ -25,5 +25,5 @@ + android:name="de.nicidienase.chaosflix.touch.favoritesimport.FavoritesImportFragment"/> diff --git a/touch/src/main/res/layout/fragment_favorites_import.xml b/touch/src/main/res/layout/fragment_favorites_import.xml index 3078938c..f78875e3 100644 --- a/touch/src/main/res/layout/fragment_favorites_import.xml +++ b/touch/src/main/res/layout/fragment_favorites_import.xml @@ -1,20 +1,41 @@ - + android:layout_height="match_parent"> + + + + + + \ No newline at end of file diff --git a/touch/src/main/res/layout/item_favorit_import.xml b/touch/src/main/res/layout/item_favorit_import.xml new file mode 100644 index 00000000..f27b3126 --- /dev/null +++ b/touch/src/main/res/layout/item_favorit_import.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "/> + + + + + \ No newline at end of file diff --git a/touch/src/main/res/layout/loading_overlay.xml b/touch/src/main/res/layout/loading_overlay.xml index eca2d8b1..585a915c 100644 --- a/touch/src/main/res/layout/loading_overlay.xml +++ b/touch/src/main/res/layout/loading_overlay.xml @@ -8,6 +8,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/inactive_tab_color" + tools:visibility="gone" tools:showIn="@layout/fragment_tab_pager_layout"> Date: Fri, 3 Jan 2020 17:30:50 +0100 Subject: [PATCH 03/21] use simpler FahrplanLecture for importing favorites --- .../common/eventimport/FahrplanLecture.kt | 35 +++---------------- .../viewmodel/FavoritesImportViewModel.kt | 16 ++++----- 2 files changed, 13 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt index de7ba99f..496118f5 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt @@ -1,35 +1,10 @@ package de.nicidienase.chaosflix.common.eventimport -import com.google.gson.annotations.SerializedName - data class FahrplanLecture( - var lectureId: String = "", + var lectureId: String? = null, var title: String, - var subtitle: String = "", - var day: Int = 0, - var room: String? = null, - var slug: String? = null, - var url: String? = null, - var startTime: Int = 0, - var duration: Int = 0, - var speakers: String? = null, - var track: String? = null, - var type: String? = null, - var lang: String? = null, - @SerializedName("abstractt") - var abstract: String = "", - var description: String = "", - var relStartTime: Int = 0, + var subtitle: String? = null, var links: String? = null, - var date: String? = null, - var dateUTC: Long = 0, - var roomIndex: Int = 0, - var recordingLicense: String? = null, - var recordingOptOut: Boolean = false -) { - - companion object { - val RECORDING_OPTOUT_ON = true - val RECORDING_OPTOUT_OFF = false - } -} + var track: String? = null, + var description: String? = null +) \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 2198c0ae..08da012a 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -60,16 +60,16 @@ class FavoritesImportViewModel( } } - private suspend fun updateConferences(conference: String) { + private suspend fun updateConferences(conferenceName: String) { val fahrplanMappings = mappingService.getFahrplanMappings() - Log.d(TAG, "Updating conferences for $conference, mappings=$fahrplanMappings") - if (fahrplanMappings.containsKey(conference)) { - fahrplanMappings[conference]?.let { keys -> - for (conf in keys) { - conferenceDao.findConferenceByAcronymSync(conf)?.let { conf -> + Log.d(TAG, "Updating conferences for $conferenceName, mappings=$fahrplanMappings") + if (fahrplanMappings.containsKey(conferenceName)) { + fahrplanMappings[conferenceName]?.let { keys -> + for (conferenceAcronym in keys) { + conferenceDao.findConferenceByAcronymSync(conferenceAcronym)?.let { conference -> val list = - downloader.updateEventsForConferencesSuspending(conf) - Log.d(TAG, "updated ${conf.acronym}, got ${list.size} events") + downloader.updateEventsForConferencesSuspending(conference) + Log.d(TAG, "updated ${conference.acronym}, got ${list.size} events") } } } From 212079d072c1f393cf56091f23fcf7b5c98bf8d1 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 01:03:53 +0100 Subject: [PATCH 04/21] add missing package definition in mockInit --- common/src/mock/java/de/nicidienase/chaosflix/mockInit.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/mock/java/de/nicidienase/chaosflix/mockInit.kt b/common/src/mock/java/de/nicidienase/chaosflix/mockInit.kt index 53dc7754..63900db4 100644 --- a/common/src/mock/java/de/nicidienase/chaosflix/mockInit.kt +++ b/common/src/mock/java/de/nicidienase/chaosflix/mockInit.kt @@ -1,3 +1,3 @@ -import de.nicidienase.chaosflix.ChaosflixApplication +package de.nicidienase.chaosflix fun stageInit(application: ChaosflixApplication) {} \ No newline at end of file From acd928c4451f6d540da41c6d2ae7c7c81ed52b54 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 01:05:10 +0100 Subject: [PATCH 05/21] check if uri is null --- .../java/de/nicidienase/chaosflix/touch/SplashActivity.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt index c2734d31..a3fd2058 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt @@ -39,7 +39,12 @@ class SplashActivity : AppCompatActivity() { when (intent.action) { Intent.ACTION_VIEW -> { - viewModel.findEventForUri(intent.data) + val uri = intent.data + if(uri != null){ + viewModel.findEventForUri(uri) + } else { + goToOverview() + } } else -> { goToOverview() From 72079a48ac3729d947105d5038a10461a13121da Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 01:14:19 +0100 Subject: [PATCH 06/21] make WatchlistItemDao dependency in FavoritesImportViewmodel redundant --- .../common/mediadata/MediaRepository.kt | 7 ++++ .../entities/watchlist/WatchlistItemDao.kt | 34 ++++++++++++++----- .../viewmodel/FavoritesImportViewModel.kt | 6 ++-- .../common/viewmodel/ViewModelFactory.kt | 1 - 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt index 03521e67..2bf1c347 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt @@ -16,6 +16,8 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence. import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.RelatedEvent import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.RelatedEventDao import de.nicidienase.chaosflix.common.mediadata.network.RecordingService +import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem +import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItemDao import de.nicidienase.chaosflix.common.util.ConferenceUtil import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent @@ -39,6 +41,7 @@ class MediaRepository( private val eventDao: EventDao by lazy { database.eventDao() } private val recordingDao: RecordingDao by lazy { database.recordingDao() } private val relatedEventDao: RelatedEventDao by lazy { database.relatedEventDao() } + private val watchlistItemDao: WatchlistItemDao by lazy { database.watchlistItemDao() } fun updateConferencesAndGroups(): SingleLiveEvent, String>> { val updateState = SingleLiveEvent, String>>() @@ -236,6 +239,10 @@ class MediaRepository( suspend fun getAllOfflineEvents(): List = database.offlineEventDao().getAllDownloadReferences() + suspend fun saveOrUpdate(watchlistItem: WatchlistItem) { + watchlistItemDao.updateOrInsert(watchlistItem) + } + enum class State { DONE, RUNNING } diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt index b4d29914..63cb0831 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt @@ -6,27 +6,43 @@ import androidx.room.Query import androidx.room.OnConflictStrategy import androidx.room.Insert import androidx.room.Delete +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.BaseDao @Dao -interface WatchlistItemDao { - @Query("SELECT * from watchlist_item") - fun getAll(): LiveData> +abstract class WatchlistItemDao: BaseDao() { @Query("SELECT * from watchlist_item") - fun getAllSync(): List + abstract fun getAll(): LiveData> + + @Query("SELECT * from watchlist_item") + abstract fun getAllSync(): List @Query("SELECT * from watchlist_item WHERE event_guid = :guid LIMIT 1") - fun getItemForEvent(guid: String): LiveData + abstract fun getItemForEvent(guid: String): LiveData @Insert(onConflict = OnConflictStrategy.REPLACE) - fun saveItem(item: WatchlistItem) + abstract fun saveItem(item: WatchlistItem) @Delete - fun deleteItem(item: WatchlistItem) + abstract fun deleteItem(item: WatchlistItem) @Query("DELETE from watchlist_item WHERE event_guid = :guid") - fun deleteItem(guid: String) + abstract fun deleteItem(guid: String) @Query("SELECT * from watchlist_item WHERE event_guid = :guid LIMIT 1") - suspend fun getItemForGuid(guid: String): WatchlistItem? + abstract suspend fun getItemForGuid(guid: String): WatchlistItem? + + override suspend fun updateOrInsertInternal(item: WatchlistItem) { + if (item.id != 0L) { + update(item) + } else { + val existingEvent = getItemForGuid(item.eventGuid) + if (existingEvent != null) { + item.id = existingEvent.id + update(item) + } else { + item.id = insert(item) + } + } + } } \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index f9577821..0e26da8c 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -9,14 +9,12 @@ import de.nicidienase.chaosflix.common.eventimport.FahrplanExport import de.nicidienase.chaosflix.common.eventimport.FahrplanLecture import de.nicidienase.chaosflix.common.mediadata.MediaRepository import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem -import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItemDao import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.SingleLiveEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class FavoritesImportViewModel( - private val watchlistItemDao: WatchlistItemDao, private val mediaRepository: MediaRepository ) : ViewModel() { @@ -48,8 +46,8 @@ class FavoritesImportViewModel( for (item in events) { Log.d(TAG, "${item.lecture.title}: ${item.selected}") val guid = item.event?.guid - if (item.selected && guid != null && watchlistItemDao.getItemForGuid(guid) == null) { - watchlistItemDao.saveItem(WatchlistItem(eventGuid = guid)) + if (item.selected && guid != null) { + mediaRepository.saveOrUpdate(WatchlistItem(eventGuid = guid)) } } state.postValue(LiveEvent(State.IMPORT_DONE)) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt index 24fdf70c..96fad2a5 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt @@ -55,7 +55,6 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider externalFilesDir ) as T FavoritesImportViewModel::class.java -> FavoritesImportViewModel( - database.watchlistItemDao(), mediaRepository ) as T SplashViewModel::class.java -> SplashViewModel(mediaRepository) as T From d20489052d88dbbafd1f2f094e64b27e8d6d4a9e Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 02:02:27 +0100 Subject: [PATCH 07/21] update styling of import page --- .../FavoritesImportFragment.kt | 2 +- .../res/layout/fragment_favorites_import.xml | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 71510dd0..926501fb 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -32,7 +32,7 @@ class FavoritesImportFragment : Fragment() { val adapter = ImportItemAdapter() binding.importList.adapter = adapter - binding.appCompatButton.setOnClickListener { + binding.importButton.setOnClickListener { viewModel.import(adapter.currentList) } diff --git a/touch/src/main/res/layout/fragment_favorites_import.xml b/touch/src/main/res/layout/fragment_favorites_import.xml index f78875e3..e8f7a215 100644 --- a/touch/src/main/res/layout/fragment_favorites_import.xml +++ b/touch/src/main/res/layout/fragment_favorites_import.xml @@ -15,21 +15,31 @@ android:id="@+id/import_list" android:layout_width="0dp" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@+id/appCompatButton" + app:layout_constraintBottom_toTopOf="@+id/import_button" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/item_favorit_import" /> - + app:layout_constraintStart_toStartOf="parent"> + + + + Date: Sat, 4 Jan 2020 02:08:34 +0100 Subject: [PATCH 08/21] formatting (ktlint) --- .../common/userdata/entities/watchlist/WatchlistItemDao.kt | 2 +- .../main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt index 63cb0831..5a72bb71 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/userdata/entities/watchlist/WatchlistItemDao.kt @@ -9,7 +9,7 @@ import androidx.room.Delete import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.BaseDao @Dao -abstract class WatchlistItemDao: BaseDao() { +abstract class WatchlistItemDao : BaseDao() { @Query("SELECT * from watchlist_item") abstract fun getAll(): LiveData> diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt index a3fd2058..56e1ac9f 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/SplashActivity.kt @@ -40,7 +40,7 @@ class SplashActivity : AppCompatActivity() { when (intent.action) { Intent.ACTION_VIEW -> { val uri = intent.data - if(uri != null){ + if (uri != null) { viewModel.findEventForUri(uri) } else { goToOverview() From 7e584706afef49bf3f8f258dd7affdc84ab4d9ec Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 20:23:51 +0100 Subject: [PATCH 09/21] Fahrplan-Import: move import to toolbar action and style list --- common/src/main/res/values/strings.xml | 3 +- .../FavoritesImportActivity.kt | 2 +- .../FavoritesImportFragment.kt | 27 ++- touch/src/main/res/layout/activity_splash.xml | 2 +- .../res/layout/fragment_favorites_import.xml | 21 +- .../main/res/layout/item_favorit_import.xml | 227 ++++++++++-------- touch/src/main/res/menu/import_menu.xml | 7 + touch/src/main/res/values/strings.xml | 2 + 8 files changed, 157 insertions(+), 134 deletions(-) create mode 100644 touch/src/main/res/menu/import_menu.xml diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 13c06351..f8b9ae84 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -47,7 +47,8 @@ Allow downloads over metered networks Automatically choose recording Export Favorites - Import Favorites + Import to Favorites Event not found Chaosflix does not collect or transmit any personalized data. + No Recording found for diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt index ddad8d4d..155f536d 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt @@ -12,7 +12,7 @@ class FavoritesImportActivity : AppCompatActivity() { setContentView(R.layout.activity_favorites_import) val toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) - supportActionBar?.title = "Import" + supportActionBar?.title = getString(R.string.import_activity_label) } companion object { diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 926501fb..a0e5b224 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -4,6 +4,9 @@ import android.content.Intent import android.os.Bundle import android.util.Log import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment @@ -12,30 +15,29 @@ import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory +import de.nicidienase.chaosflix.touch.R import de.nicidienase.chaosflix.touch.browse.adapters.ImportItemAdapter import de.nicidienase.chaosflix.touch.databinding.FragmentFavoritesImportBinding class FavoritesImportFragment : Fragment() { private lateinit var viewModel: FavoritesImportViewModel + private lateinit var adapter: ImportItemAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + setHasOptionsMenu(true) val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) binding.setLifecycleOwner(this) viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) binding.importList.layoutManager = LinearLayoutManager(context) - val adapter = ImportItemAdapter() + adapter = ImportItemAdapter() binding.importList.adapter = adapter - binding.importButton.setOnClickListener { - viewModel.import(adapter.currentList) - } - viewModel.state.observe(this, Observer { when (it.state) { FavoritesImportViewModel.State.EVENTS_FOUND -> { @@ -76,6 +78,21 @@ class FavoritesImportFragment : Fragment() { } } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.import_menu, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_item_import -> { + viewModel.import(adapter.currentList) + true + } + else -> super.onOptionsItemSelected(item) + } + } + private fun handleJson(intent: Intent) { val extra = intent.getStringExtra(Intent.EXTRA_TEXT) if (extra.isNotEmpty()) { diff --git a/touch/src/main/res/layout/activity_splash.xml b/touch/src/main/res/layout/activity_splash.xml index 032dd513..a35bdbcb 100644 --- a/touch/src/main/res/layout/activity_splash.xml +++ b/touch/src/main/res/layout/activity_splash.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - + - tools:visibility="visible"> - - + android:visibility="@{item.event == null ? View.GONE : View.VISIBLE}" + tools:visibility="visible"> - + - + + + + + + + + + android:visibility="@{item.event == null ? View.VISIBLE : View.GONE}" + tools:visibility="visible"> - - - - - - - + - + - "/> + - + - + + \ No newline at end of file diff --git a/touch/src/main/res/menu/import_menu.xml b/touch/src/main/res/menu/import_menu.xml new file mode 100644 index 00000000..aa79e5ee --- /dev/null +++ b/touch/src/main/res/menu/import_menu.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/touch/src/main/res/values/strings.xml b/touch/src/main/res/values/strings.xml index 12e265c3..44c32abb 100644 --- a/touch/src/main/res/values/strings.xml +++ b/touch/src/main/res/values/strings.xml @@ -54,4 +54,6 @@ Play-Button List of Events Delete Item + Import Favorites + Fahrplan-Import \ No newline at end of file From 4da09717e8cbfbe6158581209ed317477b25179c Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 23:37:33 +0100 Subject: [PATCH 10/21] DownloadCancelHandler: move TAG to companion object --- .../de/nicidienase/chaosflix/common/OfflineItemManager.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/OfflineItemManager.kt b/common/src/main/java/de/nicidienase/chaosflix/common/OfflineItemManager.kt index 655f00de..8129f984 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/OfflineItemManager.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/OfflineItemManager.kt @@ -170,8 +170,6 @@ class OfflineItemManager( private val offlineEventDao: OfflineEventDao, private val preferencesManager: PreferencesManager ) : BroadcastReceiver() { - private val TAG = DownloadCancelHandler::class.java.simpleName - override fun onReceive(p0: Context?, p1: Intent?) { val downloadId = p1?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) if (downloadId != null && downloadId == id) { @@ -189,6 +187,10 @@ class OfflineItemManager( p0?.unregisterReceiver(this) } } + + companion object { + private val TAG = DownloadCancelHandler::class.java.simpleName + } } private fun getMovieDir(): String { From b457099dbcafc04417763db7785387a0a4d8c60b Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Jan 2020 23:59:45 +0100 Subject: [PATCH 11/21] Make CastService Lifecycle-aware to fix leaking activity --- .../touch/playback/PlayerActivity.kt | 2 +- .../touch/browse/cast/CastService.kt | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/playback/PlayerActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/playback/PlayerActivity.kt index 01ceec7b..926d1cbf 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/playback/PlayerActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/playback/PlayerActivity.kt @@ -77,7 +77,7 @@ class PlayerActivity : AppCompatActivity() { const val ROOM = "room" const val STREAM = "stream" - val OFFLINE_URI = "recording_uri" + const val OFFLINE_URI = "recording_uri" fun launch(context: Context, event: Event, uri: String) { val i = Intent(context, PlayerActivity::class.java) diff --git a/touch/src/noFree/java/de/nicidienase/chaosflix/touch/browse/cast/CastService.kt b/touch/src/noFree/java/de/nicidienase/chaosflix/touch/browse/cast/CastService.kt index 6661f3fd..9780c110 100644 --- a/touch/src/noFree/java/de/nicidienase/chaosflix/touch/browse/cast/CastService.kt +++ b/touch/src/noFree/java/de/nicidienase/chaosflix/touch/browse/cast/CastService.kt @@ -1,7 +1,10 @@ package de.nicidienase.chaosflix.touch.browse.cast -import android.app.Activity import android.view.Menu +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Recording import de.nicidienase.chaosflix.common.mediadata.entities.streaming.StreamUrl @@ -9,15 +12,24 @@ import de.nicidienase.chaosflix.touch.browse.streaming.StreamingItem import pl.droidsonroids.casty.Casty import pl.droidsonroids.casty.MediaData -class CastService(activity: Activity, withMiniController: Boolean = true) { +class CastService(activity: AppCompatActivity, withMiniController: Boolean = true) : LifecycleObserver { - private val casty: Casty = if (withMiniController) { + private var casty: Casty? = if (withMiniController) { Casty.create(activity).withMiniController() } else { Casty.create(activity) } val connected: Boolean - get() = casty.isConnected + get() = casty?.isConnected ?: false + + init { + activity.lifecycle.addObserver(this) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + casty = null + } fun castStream(streamingItem: StreamingItem, streamUrl: StreamUrl, contentKey: String) { val contentType = getContentTypeForKey(contentKey) @@ -28,16 +40,16 @@ class CastService(activity: Activity, withMiniController: Boolean = true) { .setSubtitle(streamingItem.room.display) .addPhotoUrl(streamingItem.room.thumb) .build() - casty.player.loadMediaAndPlay(mediaData) + casty?.let { it.player.loadMediaAndPlay(mediaData) } } fun loadMediaAndPlay(recording: Recording, event: Event) { val mediaData = buildCastMediaData(recording, event) - casty.player.loadMediaAndPlay(mediaData) + casty?.let { it.player.loadMediaAndPlay(mediaData) } } fun addMediaRouteMenuItem(menu: Menu) { - casty.addMediaRouteMenuItem(menu) + casty?.let { it.addMediaRouteMenuItem(menu) } } private fun getContentTypeForKey(s: String): String? { @@ -51,7 +63,7 @@ class CastService(activity: Activity, withMiniController: Boolean = true) { } } - fun buildCastMediaData(recording: Recording, event: Event): MediaData { + private fun buildCastMediaData(recording: Recording, event: Event): MediaData { return MediaData.Builder(recording.recordingUrl) .setStreamType(MediaData.STREAM_TYPE_BUFFERED) .setContentType(recording.mimeType) From af44a40ffcc97a70fd42575676ec7ea5cb0c3736 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 5 Jan 2020 00:38:15 +0100 Subject: [PATCH 12/21] Fahrplan-Import: don't select everything by default and add a select all button instead --- .../viewmodel/FavoritesImportViewModel.kt | 39 +++++++++++++------ .../FavoritesImportFragment.kt | 25 ++++++++---- touch/src/main/res/menu/import_menu.xml | 3 ++ touch/src/main/res/values/strings.xml | 1 + 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 0e26da8c..e2f911b1 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -1,6 +1,8 @@ package de.nicidienase.chaosflix.common.viewmodel import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.gson.Gson @@ -18,7 +20,11 @@ class FavoritesImportViewModel( private val mediaRepository: MediaRepository ) : ViewModel() { - val state: SingleLiveEvent, Exception>> = SingleLiveEvent() + val state: SingleLiveEvent, String>> = SingleLiveEvent() + + private val _items = MutableLiveData>() + val items: LiveData> + get() = _items fun handleLectures(string: String) { state.postValue(LiveEvent(State.WORKING)) @@ -30,30 +36,41 @@ class FavoritesImportViewModel( val event = mediaRepository.findEventByTitle(lecture.title) ImportItem( lecture = lecture, - event = event, - selected = event != null + event = event ) } + _items.postValue(events) state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) } catch (e: Exception) { - state.postValue(LiveEvent(State.ERROR, null, e)) + state.postValue(LiveEvent(State.ERROR, null, e.message)) } } } - fun import(events: List) { + fun import() { state.postValue(LiveEvent(State.WORKING)) viewModelScope.launch(Dispatchers.IO) { - for (item in events) { - Log.d(TAG, "${item.lecture.title}: ${item.selected}") - val guid = item.event?.guid - if (item.selected && guid != null) { - mediaRepository.saveOrUpdate(WatchlistItem(eventGuid = guid)) + val items: List = _items.value?.filter { it.selected } ?: emptyList() + if(!items.isEmpty()){ + for (item in items) { + Log.d(TAG, "${item.lecture.title}: ${item.selected}") + val guid = item.event?.guid + if (item.selected && guid != null) { + mediaRepository.saveOrUpdate(WatchlistItem(eventGuid = guid)) + } } + state.postValue(LiveEvent(State.IMPORT_DONE)) + } else { + state.postValue(LiveEvent(State.ERROR, error = "No items to import")) } - state.postValue(LiveEvent(State.IMPORT_DONE)) } } + fun selectAll() { + val items = _items.value + items?.map { it.selected = it.event != null } + _items.postValue(items) + } + enum class State { WORKING, EVENTS_FOUND, diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index a0e5b224..f552811b 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar import de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory import de.nicidienase.chaosflix.touch.R @@ -31,33 +32,39 @@ class FavoritesImportFragment : Fragment() { ): View? { setHasOptionsMenu(true) val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) - binding.setLifecycleOwner(this) + binding.lifecycleOwner = this viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) binding.importList.layoutManager = LinearLayoutManager(context) adapter = ImportItemAdapter() binding.importList.adapter = adapter + viewModel.items.observe(this, Observer {events -> + adapter.submitList(events) + adapter.notifyDataSetChanged() + }) + viewModel.state.observe(this, Observer { + val errorMessage = it.error when (it.state) { FavoritesImportViewModel.State.EVENTS_FOUND -> { - it.data?.let { events -> - adapter.submitList(events) - adapter.notifyDataSetChanged() - } binding.incOverlay.loadingOverlay.visibility = View.GONE } FavoritesImportViewModel.State.WORKING -> { binding.incOverlay.loadingOverlay.visibility = View.VISIBLE } FavoritesImportViewModel.State.ERROR -> { - Log.e(TAG, "Error", it.error) + Log.e(TAG, "Error $errorMessage") } FavoritesImportViewModel.State.IMPORT_DONE -> { // TODO navigate to favorites activity?.finish() } } + + if(errorMessage != null) { + Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_SHORT).show() + } }) return binding.root @@ -86,7 +93,11 @@ class FavoritesImportFragment : Fragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_item_import -> { - viewModel.import(adapter.currentList) + viewModel.import() + true + } + R.id.menu_item_select_all -> { + viewModel.selectAll() true } else -> super.onOptionsItemSelected(item) diff --git a/touch/src/main/res/menu/import_menu.xml b/touch/src/main/res/menu/import_menu.xml index aa79e5ee..8db2d2ff 100644 --- a/touch/src/main/res/menu/import_menu.xml +++ b/touch/src/main/res/menu/import_menu.xml @@ -4,4 +4,7 @@ + \ No newline at end of file diff --git a/touch/src/main/res/values/strings.xml b/touch/src/main/res/values/strings.xml index 44c32abb..c8b72a98 100644 --- a/touch/src/main/res/values/strings.xml +++ b/touch/src/main/res/values/strings.xml @@ -56,4 +56,5 @@ Delete Item Import Favorites Fahrplan-Import + Select all \ No newline at end of file From d3337e2e04f839d3c347028a9b85d4b04ae8ce8e Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 5 Jan 2020 00:38:46 +0100 Subject: [PATCH 13/21] Update styling to fix checkbox selected color --- .../browse/adapters/ImportItemAdapter.kt | 2 +- .../layout/exo_playback_controlsoverlay.xml | 1 - .../res/layout/fragment_event_details.xml | 1 - .../res/layout/fragment_tab_pager_layout.xml | 6 ++--- touch/src/main/res/layout/toolbar.xml | 1 - touch/src/main/res/values/styles.xml | 23 ++++--------------- 6 files changed, 8 insertions(+), 26 deletions(-) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt index d72e1cbd..e786fe1a 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import de.nicidienase.chaosflix.common.ImportItem -class ImportItemAdapter(private val clickListener: ((ImportItem) -> Unit)? = null) : ListAdapter( +class ImportItemAdapter : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem === newItem override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem == newItem diff --git a/touch/src/main/res/layout/exo_playback_controlsoverlay.xml b/touch/src/main/res/layout/exo_playback_controlsoverlay.xml index 729f627d..8353b4bd 100644 --- a/touch/src/main/res/layout/exo_playback_controlsoverlay.xml +++ b/touch/src/main/res/layout/exo_playback_controlsoverlay.xml @@ -24,7 +24,6 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/touch/src/main/res/layout/toolbar.xml b/touch/src/main/res/layout/toolbar.xml index e33bc715..cab36329 100644 --- a/touch/src/main/res/layout/toolbar.xml +++ b/touch/src/main/res/layout/toolbar.xml @@ -6,7 +6,6 @@ false true true - @color/primary - @color/primary_dark - @color/accent - - - - - @style/SettingsStyle - - - - - From 15c68b0dd0cdd266f643ead3248e194462583d06 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 5 Jan 2020 01:32:47 +0100 Subject: [PATCH 14/21] prevent reloading of imports on configuration change --- .../common/viewmodel/FavoritesImportViewModel.kt | 10 ++++++++-- .../touch/favoritesimport/FavoritesImportFragment.kt | 9 +++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index e2f911b1..871bf911 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -22,14 +22,20 @@ class FavoritesImportViewModel( val state: SingleLiveEvent, String>> = SingleLiveEvent() + private var lastImport: String = "" + private val _items = MutableLiveData>() val items: LiveData> get() = _items - fun handleLectures(string: String) { + fun handleLectures(jsonImport: String) { + if(jsonImport == lastImport){ + return + } + lastImport = jsonImport state.postValue(LiveEvent(State.WORKING)) viewModelScope.launch(Dispatchers.IO) { - val export = Gson().fromJson(string, FahrplanExport::class.java) + val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) val events: List try { events = export.lectures.map { lecture: FahrplanLecture -> diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index f552811b..56811e00 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -1,5 +1,6 @@ package de.nicidienase.chaosflix.touch.favoritesimport +import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log @@ -41,7 +42,6 @@ class FavoritesImportFragment : Fragment() { viewModel.items.observe(this, Observer {events -> adapter.submitList(events) - adapter.notifyDataSetChanged() }) viewModel.state.observe(this, Observer { @@ -67,11 +67,6 @@ class FavoritesImportFragment : Fragment() { } }) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) val intent = activity?.intent when { intent?.action == Intent.ACTION_SEND -> { @@ -83,6 +78,8 @@ class FavoritesImportFragment : Fragment() { // Handle other intents, such as being started from the home screen } } + + return binding.root } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { From fcbf90ed2ca19222a85b2498f2996e69120d7980 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 5 Jan 2020 17:52:39 +0100 Subject: [PATCH 15/21] Fahrplan-Import: fix reloading of list when selecting all items --- .../viewmodel/FavoritesImportViewModel.kt | 92 ++++++++++++------- .../browse/adapters/ImportItemAdapter.kt | 15 ++- .../FavoritesImportFragment.kt | 31 +++---- 3 files changed, 88 insertions(+), 50 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 871bf911..8e8158fb 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -24,64 +24,90 @@ class FavoritesImportViewModel( private var lastImport: String = "" - private val _items = MutableLiveData>() - val items: LiveData> + private val _items = MutableLiveData?>() + val items: LiveData?> get() = _items + private val _errorMessage = MutableLiveData() + val errorMessage: LiveData + get() = _errorMessage + + private val _working = MutableLiveData() + val working: LiveData + get() = _working + fun handleLectures(jsonImport: String) { - if(jsonImport == lastImport){ + if (jsonImport == lastImport) { return } lastImport = jsonImport - state.postValue(LiveEvent(State.WORKING)) viewModelScope.launch(Dispatchers.IO) { - val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) - val events: List - try { - events = export.lectures.map { lecture: FahrplanLecture -> - val event = mediaRepository.findEventByTitle(lecture.title) - ImportItem( - lecture = lecture, - event = event - ) } - _items.postValue(events) - state.postValue(LiveEvent(State.EVENTS_FOUND, events, null)) - } catch (e: Exception) { - state.postValue(LiveEvent(State.ERROR, null, e.message)) + loading { + val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) + val events: List + try { + events = export.lectures.map { lecture: FahrplanLecture -> + val event = mediaRepository.findEventByTitle(lecture.title) + ImportItem( + lecture = lecture, + event = event, + selected = false + ) } + _items.postValue(events) + } catch (e: Exception) { + showErrorMessage(e.message ?: "An error occured while searching for recordings") + } } } } fun import() { - state.postValue(LiveEvent(State.WORKING)) viewModelScope.launch(Dispatchers.IO) { val items: List = _items.value?.filter { it.selected } ?: emptyList() - if(!items.isEmpty()){ - for (item in items) { - Log.d(TAG, "${item.lecture.title}: ${item.selected}") - val guid = item.event?.guid - if (item.selected && guid != null) { - mediaRepository.saveOrUpdate(WatchlistItem(eventGuid = guid)) + if (items.isNotEmpty()) { + loading { + for (item in items) { + Log.d(TAG, "${item.lecture.title}: ${item.selected}") + val guid = item.event?.guid + if (item.selected && guid != null) { + mediaRepository.saveOrUpdate(WatchlistItem(eventGuid = guid)) + } } + state.postValue(LiveEvent(State.IMPORT_DONE)) } - state.postValue(LiveEvent(State.IMPORT_DONE)) } else { - state.postValue(LiveEvent(State.ERROR, error = "No items to import")) + showErrorMessage("No items to import") } } } fun selectAll() { - val items = _items.value - items?.map { it.selected = it.event != null } - _items.postValue(items) + val items: List? = _items.value?.toList() + val newList = items?.map { it.copy(selected = it.event != null) } + _items.postValue(newList) + } + + private suspend fun loading(work: suspend () -> Unit) { + _working.postValue(true) + work() + _working.postValue(false) + } + + fun errorShown() { + _errorMessage.postValue(null) + } + + private fun showErrorMessage(message: String) { + val currentMessage = _errorMessage.value + if (currentMessage != null) { + _errorMessage.postValue("$currentMessage\n$message") + } else { + _errorMessage.postValue(message) + } } enum class State { - WORKING, - EVENTS_FOUND, - IMPORT_DONE, - ERROR + IMPORT_DONE } companion object { diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt index e786fe1a..755d4f1d 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -7,11 +7,16 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import de.nicidienase.chaosflix.common.ImportItem +import java.lang.NumberFormatException class ImportItemAdapter : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem === newItem - override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem == newItem + override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem): Boolean { + return oldItem.selected == newItem.selected && + oldItem.lecture == newItem.lecture && + oldItem.event == newItem.event + } }) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) @@ -29,5 +34,13 @@ class ImportItemAdapter : ListAdapter( } } + override fun getItemId(position: Int): Long { + return try { + getItem(position).lecture.lectureId?.toLong() ?: 0 + } catch (ex: NumberFormatException) { + -1 + } + } + class ViewHolder(val binding: ItemFavoritImportBinding) : RecyclerView.ViewHolder(binding.root) } \ No newline at end of file diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 56811e00..0afb5bad 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -1,9 +1,7 @@ package de.nicidienase.chaosflix.touch.favoritesimport -import android.content.Context import android.content.Intent import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -34,36 +32,37 @@ class FavoritesImportFragment : Fragment() { setHasOptionsMenu(true) val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) binding.lifecycleOwner = this - viewModel = ViewModelProviders.of(this, ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) + viewModel = ViewModelProviders.of(requireActivity(), ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) binding.importList.layoutManager = LinearLayoutManager(context) adapter = ImportItemAdapter() + adapter.setHasStableIds(true) + binding.importList.setHasFixedSize(true) binding.importList.adapter = adapter - viewModel.items.observe(this, Observer {events -> - adapter.submitList(events) + viewModel.items.observe(this, Observer { events -> + if (events != null) { + adapter.submitList(events.toList()) + } }) viewModel.state.observe(this, Observer { - val errorMessage = it.error when (it.state) { - FavoritesImportViewModel.State.EVENTS_FOUND -> { - binding.incOverlay.loadingOverlay.visibility = View.GONE - } - FavoritesImportViewModel.State.WORKING -> { - binding.incOverlay.loadingOverlay.visibility = View.VISIBLE - } - FavoritesImportViewModel.State.ERROR -> { - Log.e(TAG, "Error $errorMessage") - } FavoritesImportViewModel.State.IMPORT_DONE -> { // TODO navigate to favorites activity?.finish() } } + }) - if(errorMessage != null) { + viewModel.working.observe(this, Observer { working -> + binding.incOverlay.loadingOverlay.visibility = if (working) View.VISIBLE else View.GONE + }) + + viewModel.errorMessage.observe(this, Observer { errorMessage -> + if (errorMessage != null) { Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_SHORT).show() + viewModel.errorShown() } }) From a08147f9afadda6068063d5f3f2c9def10649263 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 7 Jan 2020 22:27:27 +0100 Subject: [PATCH 16/21] Fahrplan-Import: fix selectAll/selectNone and add test --- build.gradle | 1 + common/build.gradle | 10 +- .../viewmodel/FavoritesImportViewModel.kt | 63 ++++++++---- common/src/main/res/drawable/ic_check_box.xml | 9 ++ .../res/drawable/ic_check_box_outline.xml | 9 ++ .../common/InstantExecutorExtension.kt | 25 +++++ .../viewmodel/FavoritesImportViewModelTest.kt | 98 +++++++++++++++++++ .../browse/adapters/ImportItemAdapter.kt | 3 +- .../FavoritesImportFragment.kt | 32 +++++- .../touch/settings/SettingsFragment.kt | 2 +- .../res/layout/fragment_favorites_import.xml | 20 ++++ touch/src/main/res/menu/import_menu.xml | 8 +- touch/src/main/res/values/strings.xml | 1 + 13 files changed, 249 insertions(+), 32 deletions(-) create mode 100644 common/src/main/res/drawable/ic_check_box.xml create mode 100644 common/src/main/res/drawable/ic_check_box_outline.xml create mode 100644 common/src/test/java/de/nicidienase/chaosflix/common/InstantExecutorExtension.kt create mode 100644 common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt diff --git a/build.gradle b/build.gradle index 2e9890ba..28934bd5 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ buildscript { classpath 'com.android.tools.build:gradle:3.5.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:8.0.0" + classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.2.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/common/build.gradle b/common/build.gradle index f719d8a5..76d101ee 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: "de.mannodermaus.android-junit5" String versionString = new File("versionfile").text.trim() @@ -138,11 +139,16 @@ dependencies { debugImplementation 'com.facebook.stetho:stetho-okhttp:1.4.2' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0' - testImplementation "org.mockito:mockito-core:2.11.0" - testImplementation 'junit:junit:4.12' + testImplementation "io.mockk:mockk:1.9" testImplementation 'org.robolectric:robolectric:4.1' androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) + testImplementation "org.junit.jupiter:junit-jupiter-api:5.5.2" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.2" + + testImplementation "junit:junit:4.12" + testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.5.2" + } \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 8e8158fb..3bd3d669 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -3,6 +3,7 @@ package de.nicidienase.chaosflix.common.viewmodel import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.gson.Gson @@ -32,10 +33,17 @@ class FavoritesImportViewModel( val errorMessage: LiveData get() = _errorMessage - private val _working = MutableLiveData() + private val _working = MutableLiveData(false) val working: LiveData get() = _working + val importItemCount: LiveData = Transformations.map(items) { items -> + val selectedItems = items?.filter { it.selected && it.event != null } + return@map selectedItems?.count() ?: 0 + } + + val selectAll = MutableLiveData(true) + fun handleLectures(jsonImport: String) { if (jsonImport == lastImport) { return @@ -43,25 +51,29 @@ class FavoritesImportViewModel( lastImport = jsonImport viewModelScope.launch(Dispatchers.IO) { loading { - val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) - val events: List - try { - events = export.lectures.map { lecture: FahrplanLecture -> - val event = mediaRepository.findEventByTitle(lecture.title) - ImportItem( - lecture = lecture, - event = event, - selected = false - ) } - _items.postValue(events) - } catch (e: Exception) { - showErrorMessage(e.message ?: "An error occured while searching for recordings") - } + handleLecturesInternal(jsonImport) } } } - fun import() { + internal suspend fun handleLecturesInternal(jsonImport: String) { + val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) + val events: List + try { + events = export.lectures.map { lecture: FahrplanLecture -> + val event = mediaRepository.findEventByTitle(lecture.title) + ImportItem( + lecture = lecture, + event = event, + selected = false + ) } + _items.postValue(events) + } catch (e: Exception) { + showErrorMessage(e.message ?: "An error occured while searching for recordings") + } + } + + fun importFavorites() { viewModelScope.launch(Dispatchers.IO) { val items: List = _items.value?.filter { it.selected } ?: emptyList() if (items.isNotEmpty()) { @@ -76,17 +88,28 @@ class FavoritesImportViewModel( state.postValue(LiveEvent(State.IMPORT_DONE)) } } else { - showErrorMessage("No items to import") + showErrorMessage("No items to importFavorites") } } } - fun selectAll() { - val items: List? = _items.value?.toList() - val newList = items?.map { it.copy(selected = it.event != null) } + fun selectAll(selected: Boolean? = null) { + val items: List? = _items.value + val newList = items?.map { it.copy(selected = selected ?: (it.event != null)) } _items.postValue(newList) } + fun itemChanged(item: ImportItem) { + val items: MutableList? = _items.value?.toMutableList() + val index = items?.indexOf(item) + if (index != null) { + items[index] = items[index].copy() + } + _items.postValue(items) + } + + fun selectNone() = selectAll(false) + private suspend fun loading(work: suspend () -> Unit) { _working.postValue(true) work() diff --git a/common/src/main/res/drawable/ic_check_box.xml b/common/src/main/res/drawable/ic_check_box.xml new file mode 100644 index 00000000..9948171c --- /dev/null +++ b/common/src/main/res/drawable/ic_check_box.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/main/res/drawable/ic_check_box_outline.xml b/common/src/main/res/drawable/ic_check_box_outline.xml new file mode 100644 index 00000000..cf8bfa24 --- /dev/null +++ b/common/src/main/res/drawable/ic_check_box_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/common/src/test/java/de/nicidienase/chaosflix/common/InstantExecutorExtension.kt b/common/src/test/java/de/nicidienase/chaosflix/common/InstantExecutorExtension.kt new file mode 100644 index 00000000..1a1fb58e --- /dev/null +++ b/common/src/test/java/de/nicidienase/chaosflix/common/InstantExecutorExtension.kt @@ -0,0 +1,25 @@ +package de.nicidienase.chaosflix.common + +import androidx.arch.core.executor.ArchTaskExecutor +import androidx.arch.core.executor.TaskExecutor +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext + +class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback { + + override fun beforeEach(context: ExtensionContext?) { + ArchTaskExecutor.getInstance() + .setDelegate(object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) = runnable.run() + + override fun postToMainThread(runnable: Runnable) = runnable.run() + + override fun isMainThread(): Boolean = true + }) + } + + override fun afterEach(context: ExtensionContext?) { + ArchTaskExecutor.getInstance().setDelegate(null) + } +} diff --git a/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt b/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt new file mode 100644 index 00000000..da008f8b --- /dev/null +++ b/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt @@ -0,0 +1,98 @@ +package de.nicidienase.chaosflix.common.viewmodel + +import de.nicidienase.chaosflix.common.InstantExecutorExtension +import de.nicidienase.chaosflix.common.mediadata.MediaRepository +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.Event +import io.mockk.coEvery +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.junit5.MockKExtension +import io.mockk.slot +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class, InstantExecutorExtension::class) +internal class FavoritesImportViewModelTest { + + @RelaxedMockK + private lateinit var mediaRepository: MediaRepository + + private lateinit var favoritesImportViewModel: FavoritesImportViewModel + + @BeforeEach + fun setup() { + favoritesImportViewModel = FavoritesImportViewModel(mediaRepository) + } + + @Test + fun selectAll() { + val slot = slot() + coEvery { mediaRepository.findEventByTitle(capture(slot)) }.answers { + Event(title = slot.captured) + } + runBlocking { + favoritesImportViewModel.handleLecturesInternal(SAMPLE_JSON) + } + + favoritesImportViewModel.selectAll(null) + val items = favoritesImportViewModel.items.value ?: emptyList() + val selected = items.map { it.selected } + assertEquals(2, selected.size) + selected.map { value -> + assertEquals(true, value) + } + } + + @Test + fun unselectAll() { + val slot = slot() + coEvery { mediaRepository.findEventByTitle(capture(slot)) }.answers { + Event(title = slot.captured) + } + runBlocking { + favoritesImportViewModel.handleLecturesInternal(SAMPLE_JSON) + } + + favoritesImportViewModel.selectAll(false) + val items = favoritesImportViewModel.items.value ?: emptyList() + + assertEquals(2, items.size) + items.map { value -> + assertEquals(false, value.selected) + } + } + + companion object { + private const val SAMPLE_JSON = "{\"conference\":\"ccc36c3\",\"lectures\":[" + + "{\"lectureId\":\"11223\"," + + "\"title\":\"Opening Ceremony\"," + + "\"subtitle\":\"\",\"day\":1," + + "\"room\":\"Ada\"," + + "\"slug\":\"36c3-11223-opening_ceremony\"," + + "\"url\":\"https://fahrplan.events.ccc.de/congress/2019/Fahrplan/events/11223.html\"," + + "\"speakers\":\"bleeptrack;blinry\"," + + "\"track\":\"CCC\"," + + "\"type\":\"de\"," + + "\"lang\":\"\"," + + "\"abstract\":\"Welcome!\"," + + "\"description\":\"\"," + + "\"links\":\"2019-12-27\"}," + + "{\"lectureId\":\"11224\"," + + "\"title\":\"Closing Ceremony\"," + + "\"subtitle\":\"\"," + + "\"day\":4," + + "\"room\":\"Ada\"," + + "\"slug\":\"36c3-11224-closing_ceremony\"," + + "\"url\":\"\"," + + "\"speakers\":\"bleeptrack;blinry\"," + + "\"track\":\"CCC\"," + + "\"type\":\"de\"," + + "\"lang\":\"\"," + + "\"abstract\":\"Welcome!\"," + + "\"description\":\"\"," + + "\"links\":\"2019-12-30\"}" + + "]}" + } +} \ No newline at end of file diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt index 755d4f1d..e653db1c 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -9,7 +9,7 @@ import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import de.nicidienase.chaosflix.common.ImportItem import java.lang.NumberFormatException -class ImportItemAdapter : ListAdapter( +class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = null) : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem === newItem override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem): Boolean { @@ -31,6 +31,7 @@ class ImportItemAdapter : ListAdapter( holder.binding.checkBox.apply { isChecked = !isChecked } + onListItemClick?.invoke(item) } } diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 0afb5bad..1beb2d0d 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -33,9 +33,12 @@ class FavoritesImportFragment : Fragment() { val binding = FragmentFavoritesImportBinding.inflate(inflater, container, false) binding.lifecycleOwner = this viewModel = ViewModelProviders.of(requireActivity(), ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java) + binding.viewModel = viewModel binding.importList.layoutManager = LinearLayoutManager(context) - adapter = ImportItemAdapter() + adapter = ImportItemAdapter { + viewModel.itemChanged(it) + } adapter.setHasStableIds(true) binding.importList.setHasFixedSize(true) binding.importList.adapter = adapter @@ -46,6 +49,10 @@ class FavoritesImportFragment : Fragment() { } }) + viewModel.selectAll.observe(this, Observer { + activity?.invalidateOptionsMenu() + }) + viewModel.state.observe(this, Observer { when (it.state) { FavoritesImportViewModel.State.IMPORT_DONE -> { @@ -66,6 +73,14 @@ class FavoritesImportFragment : Fragment() { } }) + viewModel.importItemCount.observe(this, Observer { count -> + if (count == 0) { + binding.buttonImport.hide() + } else { + binding.buttonImport.show() + } + }) + val intent = activity?.intent when { intent?.action == Intent.ACTION_SEND -> { @@ -83,17 +98,24 @@ class FavoritesImportFragment : Fragment() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.import_menu, menu) + if (viewModel.selectAll.value != false) { + menu.removeItem(R.id.menu_item_unselect_all) + } else { + menu.removeItem(R.id.menu_item_select_all) + } super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.menu_item_import -> { - viewModel.import() - true - } R.id.menu_item_select_all -> { viewModel.selectAll() + viewModel.selectAll.value = false + true + } + R.id.menu_item_unselect_all -> { + viewModel.selectNone() + viewModel.selectAll.value = true true } else -> super.onOptionsItemSelected(item) diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/settings/SettingsFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/settings/SettingsFragment.kt index 1423438f..3941e6a2 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/settings/SettingsFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/settings/SettingsFragment.kt @@ -119,7 +119,7 @@ class SettingsFragment : PreferenceFragmentCompat() { if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { importFavorites() } else { - Snackbar.make(listView, "Cannot import without Storage Permission.", Snackbar.LENGTH_SHORT).show() + Snackbar.make(listView, "Cannot importFavorites without Storage Permission.", Snackbar.LENGTH_SHORT).show() } } PERMISSION_REQUEST_EXPORT_FAVORITES -> { diff --git a/touch/src/main/res/layout/fragment_favorites_import.xml b/touch/src/main/res/layout/fragment_favorites_import.xml index b8174a59..4e3012bd 100644 --- a/touch/src/main/res/layout/fragment_favorites_import.xml +++ b/touch/src/main/res/layout/fragment_favorites_import.xml @@ -3,6 +3,12 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + + @@ -22,7 +28,21 @@ app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/item_favorit_import" /> + + + diff --git a/touch/src/main/res/menu/import_menu.xml b/touch/src/main/res/menu/import_menu.xml index 8db2d2ff..008a3fd7 100644 --- a/touch/src/main/res/menu/import_menu.xml +++ b/touch/src/main/res/menu/import_menu.xml @@ -1,10 +1,12 @@ - + \ No newline at end of file diff --git a/touch/src/main/res/values/strings.xml b/touch/src/main/res/values/strings.xml index c8b72a98..4862507e 100644 --- a/touch/src/main/res/values/strings.xml +++ b/touch/src/main/res/values/strings.xml @@ -57,4 +57,5 @@ Import Favorites Fahrplan-Import Select all + Bookmark \ No newline at end of file From 9f51e7be92606d69ea478a09f90a4247544f4832 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Apr 2020 18:43:24 +0200 Subject: [PATCH 17/21] show progress when loading fahrplan-import --- .../common/eventimport/FahrplanLecture.kt | 15 ++++++---- .../common/mediadata/MediaRepository.kt | 7 +++-- .../viewmodel/FavoritesImportViewModel.kt | 8 ++++- .../FavoritesImportFragment.kt | 22 ++++++++++---- touch/src/main/res/layout/loading_overlay.xml | 30 ++++++++++++++++--- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt index 496118f5..eae81966 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt @@ -1,10 +1,13 @@ package de.nicidienase.chaosflix.common.eventimport +import com.google.gson.annotations.SerializedName + data class FahrplanLecture( - var lectureId: String? = null, - var title: String, - var subtitle: String? = null, - var links: String? = null, - var track: String? = null, - var description: String? = null + @SerializedName("lecture_id") + var lectureId: String? = null, + var title: String, + var subtitle: String? = null, + var links: String? = null, + var track: String? = null, + var description: String? = null ) \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt index 04eb0b60..54d65d2f 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt @@ -226,16 +226,19 @@ class MediaRepository( suspend fun findEventByTitle(title: String): Event? { var event: Event? = eventDao.findEventByTitleSuspend(title) if (event == null) { - event = searchEvent(title) + event = searchEvent(title, true) } return event } - private suspend fun searchEvent(queryString: String): Event? { + private suspend fun searchEvent(queryString: String, updateConference: Boolean = false): Event? { val searchEvents = recordingApi.searchEvents(queryString) if (searchEvents.events.isNotEmpty()) { val eventDto = searchEvents.events[0] val conference = updateConferencesAndGet(eventDto.conferenceUrl.split("/").last()) + if(updateConference && conference != null){ + updateEventsForConference(conference) + } if (conference?.id != null) { var event = Event(eventDto, conference.id) eventDao.updateOrInsert(event) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 3bd3d669..33718e2f 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -37,6 +37,10 @@ class FavoritesImportViewModel( val working: LiveData get() = _working + private val _processCount = MutableLiveData> (0 to 0) + val processCount: LiveData> + get() = _processCount + val importItemCount: LiveData = Transformations.map(items) { items -> val selectedItems = items?.filter { it.selected && it.event != null } return@map selectedItems?.count() ?: 0 @@ -59,9 +63,11 @@ class FavoritesImportViewModel( internal suspend fun handleLecturesInternal(jsonImport: String) { val export = Gson().fromJson(jsonImport, FahrplanExport::class.java) val events: List + _processCount.postValue(0 to export.lectures.size) try { - events = export.lectures.map { lecture: FahrplanLecture -> + events = export.lectures.mapIndexed { index, lecture -> val event = mediaRepository.findEventByTitle(lecture.title) + _processCount.postValue(index to export.lectures.size) ImportItem( lecture = lecture, event = event, diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 1beb2d0d..e8993247 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -2,6 +2,7 @@ package de.nicidienase.chaosflix.touch.favoritesimport import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -43,17 +44,17 @@ class FavoritesImportFragment : Fragment() { binding.importList.setHasFixedSize(true) binding.importList.adapter = adapter - viewModel.items.observe(this, Observer { events -> + viewModel.items.observe(viewLifecycleOwner, Observer { events -> if (events != null) { adapter.submitList(events.toList()) } }) - viewModel.selectAll.observe(this, Observer { + viewModel.selectAll.observe(viewLifecycleOwner, Observer { activity?.invalidateOptionsMenu() }) - viewModel.state.observe(this, Observer { + viewModel.state.observe(viewLifecycleOwner, Observer { when (it.state) { FavoritesImportViewModel.State.IMPORT_DONE -> { // TODO navigate to favorites @@ -62,18 +63,27 @@ class FavoritesImportFragment : Fragment() { } }) - viewModel.working.observe(this, Observer { working -> + viewModel.working.observe(viewLifecycleOwner, Observer { working -> binding.incOverlay.loadingOverlay.visibility = if (working) View.VISIBLE else View.GONE }) + viewModel.processCount.observe(viewLifecycleOwner, Observer {pair -> + Log.i(TAG,"Progress ${pair.first}/${pair.second}") + binding.incOverlay.progressbar.apply { + if(pair.second != 0){ + visibility = View.VISIBLE + progress = (100 * pair.first / pair.second) + } + } + }) - viewModel.errorMessage.observe(this, Observer { errorMessage -> + viewModel.errorMessage.observe(viewLifecycleOwner, Observer { errorMessage -> if (errorMessage != null) { Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_SHORT).show() viewModel.errorShown() } }) - viewModel.importItemCount.observe(this, Observer { count -> + viewModel.importItemCount.observe(viewLifecycleOwner, Observer { count -> if (count == 0) { binding.buttonImport.hide() } else { diff --git a/touch/src/main/res/layout/loading_overlay.xml b/touch/src/main/res/layout/loading_overlay.xml index 585a915c..6aa4ad64 100644 --- a/touch/src/main/res/layout/loading_overlay.xml +++ b/touch/src/main/res/layout/loading_overlay.xml @@ -3,20 +3,42 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> - + app:duration="2000" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + + + + From 8c3d1822395c0d615ebab9a2e550792f994163fa Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Apr 2020 19:57:12 +0200 Subject: [PATCH 18/21] formatting --- .../nicidienase/chaosflix/common/ImportItem.kt | 2 +- .../common/eventimport/FahrplanExport.kt | 2 +- .../common/eventimport/FahrplanLecture.kt | 16 ++++++++-------- .../common/mediadata/MediaRepository.kt | 2 +- .../common/viewmodel/FavoritesImportViewModel.kt | 5 ++--- .../common/viewmodel/ViewModelFactory.kt | 2 +- .../viewmodel/FavoritesImportViewModelTest.kt | 2 +- .../touch/browse/adapters/ImportItemAdapter.kt | 4 ++-- .../favoritesimport/FavoritesImportActivity.kt | 2 +- .../favoritesimport/FavoritesImportFragment.kt | 10 +++++----- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt b/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt index 4260b966..ed300a3c 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/ImportItem.kt @@ -7,4 +7,4 @@ data class ImportItem( val lecture: FahrplanLecture, var event: Event?, var selected: Boolean = false -) \ No newline at end of file +) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt index 08cdfe49..7990d691 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanExport.kt @@ -3,4 +3,4 @@ package de.nicidienase.chaosflix.common.eventimport data class FahrplanExport( val conference: String, val lectures: List -) \ No newline at end of file +) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt index eae81966..9b6d8a0b 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/eventimport/FahrplanLecture.kt @@ -3,11 +3,11 @@ package de.nicidienase.chaosflix.common.eventimport import com.google.gson.annotations.SerializedName data class FahrplanLecture( - @SerializedName("lecture_id") - var lectureId: String? = null, - var title: String, - var subtitle: String? = null, - var links: String? = null, - var track: String? = null, - var description: String? = null -) \ No newline at end of file + @SerializedName("lecture_id") + var lectureId: String? = null, + var title: String, + var subtitle: String? = null, + var links: String? = null, + var track: String? = null, + var description: String? = null +) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt index 54d65d2f..16337ff6 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/MediaRepository.kt @@ -236,7 +236,7 @@ class MediaRepository( if (searchEvents.events.isNotEmpty()) { val eventDto = searchEvents.events[0] val conference = updateConferencesAndGet(eventDto.conferenceUrl.split("/").last()) - if(updateConference && conference != null){ + if (updateConference && conference != null) { updateEventsForConference(conference) } if (conference?.id != null) { diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 33718e2f..5bdea46c 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.viewModelScope import com.google.gson.Gson import de.nicidienase.chaosflix.common.ImportItem import de.nicidienase.chaosflix.common.eventimport.FahrplanExport -import de.nicidienase.chaosflix.common.eventimport.FahrplanLecture import de.nicidienase.chaosflix.common.mediadata.MediaRepository import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem import de.nicidienase.chaosflix.common.util.LiveEvent @@ -37,8 +36,8 @@ class FavoritesImportViewModel( val working: LiveData get() = _working - private val _processCount = MutableLiveData> (0 to 0) - val processCount: LiveData> + private val _processCount = MutableLiveData> (0 to 0) + val processCount: LiveData> get() = _processCount val importItemCount: LiveData = Transformations.map(items) { items -> diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt index 96fad2a5..1f66b6fb 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/ViewModelFactory.kt @@ -67,4 +67,4 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider } companion object : SingletonHolder(::ViewModelFactory) -} \ No newline at end of file +} diff --git a/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt b/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt index da008f8b..33859f18 100644 --- a/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt +++ b/common/src/test/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModelTest.kt @@ -95,4 +95,4 @@ internal class FavoritesImportViewModelTest { "\"links\":\"2019-12-30\"}" + "]}" } -} \ No newline at end of file +} diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt index e653db1c..c66b030b 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -5,8 +5,8 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import de.nicidienase.chaosflix.common.ImportItem +import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import java.lang.NumberFormatException class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = null) : ListAdapter( @@ -44,4 +44,4 @@ class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = n } class ViewHolder(val binding: ItemFavoritImportBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt index 155f536d..2baddee5 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportActivity.kt @@ -18,4 +18,4 @@ class FavoritesImportActivity : AppCompatActivity() { companion object { private val TAG = FavoritesImportActivity::class.java.simpleName } -} \ No newline at end of file +} diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index e8993247..6f360463 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -66,11 +66,11 @@ class FavoritesImportFragment : Fragment() { viewModel.working.observe(viewLifecycleOwner, Observer { working -> binding.incOverlay.loadingOverlay.visibility = if (working) View.VISIBLE else View.GONE }) - viewModel.processCount.observe(viewLifecycleOwner, Observer {pair -> - Log.i(TAG,"Progress ${pair.first}/${pair.second}") + viewModel.processCount.observe(viewLifecycleOwner, Observer { pair -> + Log.i(TAG, "Progress ${pair.first}/${pair.second}") binding.incOverlay.progressbar.apply { - if(pair.second != 0){ - visibility = View.VISIBLE + if (pair.second != 0) { + visibility = View.VISIBLE progress = (100 * pair.first / pair.second) } } @@ -142,4 +142,4 @@ class FavoritesImportFragment : Fragment() { companion object { private val TAG = FavoritesImportFragment::class.java.simpleName } -} \ No newline at end of file +} From 001c9ced07bfd01476e4e6de14b95cd39ae65f19 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Apr 2020 20:03:13 +0200 Subject: [PATCH 19/21] reorder CI steps --- .circleci/config.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 15f2efcb..c5c047ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -249,17 +249,15 @@ workflows: build_test_publish: jobs: - - build - - check: + - check + - test + - build: requires: - - build - - test: - requires: - - build + - check + - test - publish-appcenter: requires: - - test - - check + - build filters: branches: only: @@ -267,8 +265,7 @@ workflows: - develop - publish-play: requires: - - test - - check + - build filters: branches: only: From 10d24e097bf21d28fa3f14e45a712c592098cf1c Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Apr 2020 20:11:20 +0200 Subject: [PATCH 20/21] add checkout build step --- .circleci/config.yml | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c5c047ac..65f71e5e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,6 +35,37 @@ references: jobs: + setup: + <<: *container_config + steps: + - checkout + + - restore_cache: + <<: *general_cache_key + + - run: + <<: *setup_env + + - run: + name: Download Dependencies + command: | + ./gradlew $KEY_CONFIG $SIGN_CONFIG \ + -PversionCode=$VERSION_CODE_TOUCH \ + -PversionName=${VERSION_NAME} \ + androidDependencies + + - save_cache: + <<: *general_cache_key + paths: + - "~/.gradle" + - "~/.m2" + - "/opt/android-sdk-linux/licenses/" + + - persist_to_workspace: + root: *workspace_root + paths: + - . + build: <<: *container_config steps: @@ -249,8 +280,13 @@ workflows: build_test_publish: jobs: - - check - - test + - setup + - check: + requires: + - setup + - test: + requires: + - setup - build: requires: - check From 0f07d5f3684ec63d621defe701e9168761770220 Mon Sep 17 00:00:00 2001 From: Felix Date: Sat, 4 Apr 2020 23:08:55 +0200 Subject: [PATCH 21/21] add error message for unresolved imports --- .../common/viewmodel/FavoritesImportViewModel.kt | 4 ++++ .../touch/browse/adapters/ImportItemAdapter.kt | 6 +++++- .../browse/eventslist/EventsListFragment.java | 1 - .../favoritesimport/FavoritesImportFragment.kt | 15 +++++++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt index 5bdea46c..d238b0a3 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/viewmodel/FavoritesImportViewModel.kt @@ -134,6 +134,10 @@ class FavoritesImportViewModel( } } + fun unavailableItemClicked(item: ImportItem) { + showErrorMessage("No recording for ${item.lecture.title} found, maybe it was not recorded or it has not been published yet.") + } + enum class State { IMPORT_DONE } diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt index c66b030b..2db0726f 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/adapters/ImportItemAdapter.kt @@ -9,7 +9,8 @@ import de.nicidienase.chaosflix.common.ImportItem import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding import java.lang.NumberFormatException -class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = null) : ListAdapter( +class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = null, private val onLectureClick: ((ImportItem) -> Unit)? = null) : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ImportItem, newItem: ImportItem) = oldItem === newItem override fun areContentsTheSame(oldItem: ImportItem, newItem: ImportItem): Boolean { @@ -33,6 +34,9 @@ class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = n } onListItemClick?.invoke(item) } + holder.binding.importItemLecture.setOnClickListener { + onLectureClick?.invoke(item) + } } override fun getItemId(position: Int): Long { diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/eventslist/EventsListFragment.java b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/eventslist/EventsListFragment.java index fa29056e..f3b483c5 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/eventslist/EventsListFragment.java +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/browse/eventslist/EventsListFragment.java @@ -125,7 +125,6 @@ public class EventsListFragment extends BrowseFragment implements SearchView.OnQ } else if (type == TYPE_EVENTS) { { setupToolbar(binding.incToolbar.toolbar, conference.getTitle(), false); -// eventAdapter.setShowTags(conference.getTagsUsefull()); getViewModel().getEventsforConference(conference).observe(this, events -> { if(events != null){ setEvents(events); diff --git a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt index 6f360463..3a9930e6 100644 --- a/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt +++ b/touch/src/main/java/de/nicidienase/chaosflix/touch/favoritesimport/FavoritesImportFragment.kt @@ -9,11 +9,13 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.TextView import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar +import de.nicidienase.chaosflix.common.ImportItem import de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel import de.nicidienase.chaosflix.common.viewmodel.ViewModelFactory import de.nicidienase.chaosflix.touch.R @@ -37,9 +39,13 @@ class FavoritesImportFragment : Fragment() { binding.viewModel = viewModel binding.importList.layoutManager = LinearLayoutManager(context) - adapter = ImportItemAdapter { + val onItemClick: (ImportItem) -> Unit = { viewModel.itemChanged(it) } + val onLectureClick: (ImportItem) -> Unit = { + viewModel.unavailableItemClicked(it) + } + adapter = ImportItemAdapter(onListItemClick = onItemClick, onLectureClick = onLectureClick) adapter.setHasStableIds(true) binding.importList.setHasFixedSize(true) binding.importList.adapter = adapter @@ -78,7 +84,12 @@ class FavoritesImportFragment : Fragment() { viewModel.errorMessage.observe(viewLifecycleOwner, Observer { errorMessage -> if (errorMessage != null) { - Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_SHORT).show() + Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_LONG).apply { + view.findViewById(com.google.android.material.R.id.snackbar_text).maxLines = 5 + setAction("OK", View.OnClickListener { + this.dismiss() + }).show() + } viewModel.errorShown() } })