mirror of
https://github.com/NiciDieNase/chaosflix
synced 2024-11-27 06:30:29 +00:00
add activity to import events from Fahrplan-App
This commit is contained in:
parent
0441c4d4e9
commit
f92cf0e31c
15 changed files with 378 additions and 36 deletions
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package de.nicidienase.chaosflix.common.eventimport
|
||||
|
||||
data class FahrplanExport(
|
||||
val conference: String,
|
||||
val lectures: List<FahrplanLecture>
|
||||
)
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -55,6 +55,12 @@ abstract class EventDao : BaseDao<Event>() {
|
|||
@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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<String, List<String>>
|
||||
}
|
|
@ -21,6 +21,9 @@ interface RecordingService {
|
|||
@GET("public/conferences/{name}")
|
||||
fun getConferenceByName(@Path("name") name: String): Call<ConferenceDto>
|
||||
|
||||
@GET("public/conferences/{name}")
|
||||
suspend fun getConferenceByNameSuspending(@Path("name") name: String): ConferenceDto?
|
||||
|
||||
@GET("public/events/{id}")
|
||||
fun getEvent(@Path("id") id: Long): Call<EventDto>
|
||||
|
||||
|
|
|
@ -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<LiveEvent<DownloaderState, List<Event>, String>> {
|
||||
val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<Event>, String>>()
|
||||
updateState.postValue(LiveEvent(DownloaderState.RUNNING))
|
||||
threadHandler.runOnBackgroundThread {
|
||||
val response: Response<ConferenceDto>?
|
||||
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<Event> {
|
||||
val conferenceByName = recordingApi.getConferenceByNameSuspending(conference.acronym)
|
||||
val events = conferenceByName?.events
|
||||
return if (events != null) {
|
||||
saveEvents(conference, events)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRecordingsForEvent(event: Event):
|
||||
LiveData<LiveEvent<DownloaderState, List<Recording>, String>> {
|
||||
val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<Recording>, String>>()
|
||||
|
|
|
@ -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<LiveEvent<State, List<Event>, Exception>> = SingleLiveEvent()
|
||||
|
||||
fun handleUrls(urls: List<String>) {
|
||||
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<Event>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
@ -24,27 +23,51 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider
|
|||
private val preferencesManager =
|
||||
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 <T : ViewModel?> create(modelClass: Class<T>): 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. " +
|
||||
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}")
|
||||
" Request: ${modelClass.canonicalName}"
|
||||
)
|
||||
}
|
||||
}
|
||||
companion object : SingletonHolder<ViewModelFactory, Context>(::ViewModelFactory)
|
||||
|
||||
}
|
||||
companion object : SingletonHolder<ViewModelFactory, Context>(::ViewModelFactory)
|
||||
}
|
|
@ -59,6 +59,13 @@
|
|||
android:parentActivityName=".browse.BrowseActivity"/>
|
||||
|
||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||
<activity android:name=".FavoritesImportActivity" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/json" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="de.nicidienase.chaosflix.common.mediadata.sync.DownloadJobService"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package de.nicidienase.chaosflix.touch
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
|
||||
class FavoritesImportActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_favorites_import)
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.title = "Import"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = FavoritesImportActivity::class.java.simpleName
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
29
touch/src/main/res/layout/activity_favorites_import.xml
Normal file
29
touch/src/main/res/layout/activity_favorites_import.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/import_root"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:id="@+id/inc_toolbar"
|
||||
layout="@layout/toolbar"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<fragment
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="de.nicidienase.chaosflix.touch.FavoritesImportFragment"/>
|
||||
</LinearLayout>
|
20
touch/src/main/res/layout/fragment_favorites_import.xml
Normal file
20
touch/src/main/res/layout/fragment_favorites_import.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/import_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_event_cardview" />
|
||||
|
||||
<include
|
||||
android:id="@+id/inc_overlay"
|
||||
layout="@layout/loading_overlay"/>
|
||||
|
||||
</FrameLayout>
|
||||
</layout>
|
Loading…
Reference in a new issue