mirror of
https://github.com/NiciDieNase/chaosflix
synced 2025-02-18 14:08:26 +00:00
Merge branch 'develop' into feature/navigation
This commit is contained in:
commit
7bbb2100c6
34 changed files with 981 additions and 67 deletions
|
@ -35,6 +35,37 @@ references:
|
||||||
|
|
||||||
jobs:
|
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:
|
build:
|
||||||
<<: *container_config
|
<<: *container_config
|
||||||
steps:
|
steps:
|
||||||
|
@ -249,17 +280,20 @@ workflows:
|
||||||
|
|
||||||
build_test_publish:
|
build_test_publish:
|
||||||
jobs:
|
jobs:
|
||||||
- build
|
- setup
|
||||||
- check:
|
- check:
|
||||||
requires:
|
requires:
|
||||||
- build
|
- setup
|
||||||
- test:
|
- test:
|
||||||
requires:
|
requires:
|
||||||
- build
|
- setup
|
||||||
|
- build:
|
||||||
|
requires:
|
||||||
|
- check
|
||||||
|
- test
|
||||||
- publish-appcenter:
|
- publish-appcenter:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- build
|
||||||
- check
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
@ -267,8 +301,7 @@ workflows:
|
||||||
- develop
|
- develop
|
||||||
- publish-play:
|
- publish-play:
|
||||||
requires:
|
requires:
|
||||||
- test
|
- build
|
||||||
- check
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
|
|
@ -147,6 +147,7 @@ dependencies {
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.6.3'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.3'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson: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.google.android.exoplayer:exoplayer:2.9.6'
|
||||||
|
|
||||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
|
@ -175,9 +176,17 @@ dependencies {
|
||||||
testImplementation "junit:junit:4.13"
|
testImplementation "junit:junit:4.13"
|
||||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.6.0"
|
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.6.0"
|
||||||
|
|
||||||
|
testImplementation "io.mockk:mockk:1.9"
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.1'
|
testImplementation 'org.robolectric:robolectric:4.1'
|
||||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
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"
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -173,8 +173,6 @@ class OfflineItemManager(
|
||||||
private val offlineEventDao: OfflineEventDao,
|
private val offlineEventDao: OfflineEventDao,
|
||||||
private val preferencesManager: PreferencesManager
|
private val preferencesManager: PreferencesManager
|
||||||
) : BroadcastReceiver() {
|
) : BroadcastReceiver() {
|
||||||
private val TAG = DownloadCancelHandler::class.java.simpleName
|
|
||||||
|
|
||||||
override fun onReceive(p0: Context?, p1: Intent?) {
|
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||||
val downloadId = p1?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)
|
val downloadId = p1?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)
|
||||||
if (downloadId != null && downloadId == id) {
|
if (downloadId != null && downloadId == id) {
|
||||||
|
@ -192,6 +190,10 @@ class OfflineItemManager(
|
||||||
p0?.unregisterReceiver(this)
|
p0?.unregisterReceiver(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = DownloadCancelHandler::class.java.simpleName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMovieDir(): String {
|
private fun getMovieDir(): String {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package de.nicidienase.chaosflix.common.eventimport
|
||||||
|
|
||||||
|
data class FahrplanExport(
|
||||||
|
val conference: String,
|
||||||
|
val lectures: List<FahrplanLecture>
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
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
|
||||||
|
)
|
|
@ -18,6 +18,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.RelatedEvent
|
||||||
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.RelatedEventDao
|
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.RelatedEventDao
|
||||||
import de.nicidienase.chaosflix.common.mediadata.network.RecordingService
|
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.ConferenceUtil
|
||||||
import de.nicidienase.chaosflix.common.util.LiveEvent
|
import de.nicidienase.chaosflix.common.util.LiveEvent
|
||||||
import de.nicidienase.chaosflix.common.util.SingleLiveEvent
|
import de.nicidienase.chaosflix.common.util.SingleLiveEvent
|
||||||
|
@ -42,6 +44,7 @@ class MediaRepository(
|
||||||
private val eventDao: EventDao by lazy { database.eventDao() }
|
private val eventDao: EventDao by lazy { database.eventDao() }
|
||||||
private val recordingDao: RecordingDao by lazy { database.recordingDao() }
|
private val recordingDao: RecordingDao by lazy { database.recordingDao() }
|
||||||
private val relatedEventDao: RelatedEventDao by lazy { database.relatedEventDao() }
|
private val relatedEventDao: RelatedEventDao by lazy { database.relatedEventDao() }
|
||||||
|
private val watchlistItemDao: WatchlistItemDao by lazy { database.watchlistItemDao() }
|
||||||
|
|
||||||
fun updateConferencesAndGroups(): SingleLiveEvent<LiveEvent<State, List<Conference>, String>> {
|
fun updateConferencesAndGroups(): SingleLiveEvent<LiveEvent<State, List<Conference>, String>> {
|
||||||
val updateState = SingleLiveEvent<LiveEvent<State, List<Conference>, String>>()
|
val updateState = SingleLiveEvent<LiveEvent<State, List<Conference>, String>>()
|
||||||
|
@ -232,11 +235,22 @@ class MediaRepository(
|
||||||
return conferenceDao.findConferenceByAcronymSuspend(data.lastPathSegment)
|
return conferenceDao.findConferenceByAcronymSuspend(data.lastPathSegment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun searchEvent(queryString: String): Event? {
|
suspend fun findEventByTitle(title: String): Event? {
|
||||||
|
var event: Event? = eventDao.findEventByTitleSuspend(title)
|
||||||
|
if (event == null) {
|
||||||
|
event = searchEvent(title, true)
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun searchEvent(queryString: String, updateConference: Boolean = false): Event? {
|
||||||
val searchEvents = recordingApi.searchEvents(queryString)
|
val searchEvents = recordingApi.searchEvents(queryString)
|
||||||
if (searchEvents.events.isNotEmpty()) {
|
if (searchEvents.events.isNotEmpty()) {
|
||||||
val eventDto = searchEvents.events[0]
|
val eventDto = searchEvents.events[0]
|
||||||
val conference = updateConferencesAndGet(eventDto.conferenceUrl.split("/").last())
|
val conference = updateConferencesAndGet(eventDto.conferenceUrl.split("/").last())
|
||||||
|
if (updateConference && conference != null) {
|
||||||
|
updateEventsForConference(conference)
|
||||||
|
}
|
||||||
if (conference?.id != null) {
|
if (conference?.id != null) {
|
||||||
val event = Event(eventDto, conference.id)
|
val event = Event(eventDto, conference.id)
|
||||||
eventDao.updateOrInsert(event)
|
eventDao.updateOrInsert(event)
|
||||||
|
@ -248,6 +262,10 @@ class MediaRepository(
|
||||||
|
|
||||||
suspend fun getAllOfflineEvents(): List<Long> = database.offlineEventDao().getAllDownloadReferences()
|
suspend fun getAllOfflineEvents(): List<Long> = database.offlineEventDao().getAllDownloadReferences()
|
||||||
|
|
||||||
|
suspend fun saveOrUpdate(watchlistItem: WatchlistItem) {
|
||||||
|
watchlistItemDao.updateOrInsert(watchlistItem)
|
||||||
|
}
|
||||||
|
|
||||||
fun getReleatedEvents(event: Event, viewModelScope: CoroutineScope): LiveData<List<Event>> {
|
fun getReleatedEvents(event: Event, viewModelScope: CoroutineScope): LiveData<List<Event>> {
|
||||||
val data = MutableLiveData<List<Event>>()
|
val data = MutableLiveData<List<Event>>()
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
|
@ -61,6 +61,15 @@ abstract class EventDao : BaseDao<Event>() {
|
||||||
@Query("DElETE FROM event")
|
@Query("DElETE FROM event")
|
||||||
abstract fun delete()
|
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?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM event WHERE title LIKE :title LIMIT 1")
|
||||||
|
abstract fun findSingleEventByTitle(title: String): LiveData<Event?>
|
||||||
|
|
||||||
override suspend fun updateOrInsertInternal(item: Event) {
|
override suspend fun updateOrInsertInternal(item: Event) {
|
||||||
if (item.id != 0L) {
|
if (item.id != 0L) {
|
||||||
update(item)
|
update(item)
|
||||||
|
|
|
@ -6,24 +6,43 @@ import androidx.room.Delete
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.BaseDao
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface WatchlistItemDao {
|
abstract class WatchlistItemDao : BaseDao<WatchlistItem>() {
|
||||||
@Query("SELECT * from watchlist_item")
|
|
||||||
fun getAll(): LiveData<List<WatchlistItem>>
|
|
||||||
|
|
||||||
@Query("SELECT * from watchlist_item")
|
@Query("SELECT * from watchlist_item")
|
||||||
fun getAllSync(): List<WatchlistItem>
|
abstract fun getAll(): LiveData<List<WatchlistItem>>
|
||||||
|
|
||||||
|
@Query("SELECT * from watchlist_item")
|
||||||
|
abstract fun getAllSync(): List<WatchlistItem>
|
||||||
|
|
||||||
@Query("SELECT * from watchlist_item WHERE event_guid = :guid LIMIT 1")
|
@Query("SELECT * from watchlist_item WHERE event_guid = :guid LIMIT 1")
|
||||||
fun getItemForEvent(guid: String): LiveData<WatchlistItem?>
|
abstract fun getItemForEvent(guid: String): LiveData<WatchlistItem?>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun saveItem(item: WatchlistItem)
|
abstract fun saveItem(item: WatchlistItem)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteItem(item: WatchlistItem)
|
abstract fun deleteItem(item: WatchlistItem)
|
||||||
|
|
||||||
@Query("DELETE from watchlist_item WHERE event_guid = :guid")
|
@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")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
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
|
||||||
|
import de.nicidienase.chaosflix.common.ImportItem
|
||||||
|
import de.nicidienase.chaosflix.common.eventimport.FahrplanExport
|
||||||
|
import de.nicidienase.chaosflix.common.mediadata.MediaRepository
|
||||||
|
import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem
|
||||||
|
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 mediaRepository: MediaRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val state: SingleLiveEvent<LiveEvent<State, List<ImportItem>, String>> = SingleLiveEvent()
|
||||||
|
|
||||||
|
private var lastImport: String = ""
|
||||||
|
|
||||||
|
private val _items = MutableLiveData<List<ImportItem>?>()
|
||||||
|
val items: LiveData<List<ImportItem>?>
|
||||||
|
get() = _items
|
||||||
|
|
||||||
|
private val _errorMessage = MutableLiveData<String?>()
|
||||||
|
val errorMessage: LiveData<String?>
|
||||||
|
get() = _errorMessage
|
||||||
|
|
||||||
|
private val _working = MutableLiveData<Boolean>(false)
|
||||||
|
val working: LiveData<Boolean>
|
||||||
|
get() = _working
|
||||||
|
|
||||||
|
private val _processCount = MutableLiveData<Pair<Int, Int>> (0 to 0)
|
||||||
|
val processCount: LiveData<Pair<Int, Int>>
|
||||||
|
get() = _processCount
|
||||||
|
|
||||||
|
val importItemCount: LiveData<Int> = Transformations.map(items) { items ->
|
||||||
|
val selectedItems = items?.filter { it.selected && it.event != null }
|
||||||
|
return@map selectedItems?.count() ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectAll = MutableLiveData<Boolean>(true)
|
||||||
|
|
||||||
|
fun handleLectures(jsonImport: String) {
|
||||||
|
if (jsonImport == lastImport) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastImport = jsonImport
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
loading {
|
||||||
|
handleLecturesInternal(jsonImport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun handleLecturesInternal(jsonImport: String) {
|
||||||
|
val export = Gson().fromJson(jsonImport, FahrplanExport::class.java)
|
||||||
|
val events: List<ImportItem>
|
||||||
|
_processCount.postValue(0 to export.lectures.size)
|
||||||
|
try {
|
||||||
|
events = export.lectures.mapIndexed { index, lecture ->
|
||||||
|
val event = mediaRepository.findEventByTitle(lecture.title)
|
||||||
|
_processCount.postValue(index to export.lectures.size)
|
||||||
|
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<ImportItem> = _items.value?.filter { it.selected } ?: emptyList()
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showErrorMessage("No items to importFavorites")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectAll(selected: Boolean? = null) {
|
||||||
|
val items: List<ImportItem>? = _items.value
|
||||||
|
val newList = items?.map { it.copy(selected = selected ?: (it.event != null)) }
|
||||||
|
_items.postValue(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun itemChanged(item: ImportItem) {
|
||||||
|
val items: MutableList<ImportItem>? = _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()
|
||||||
|
_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = FavoritesImportViewModel::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,30 +22,50 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider
|
||||||
private val database by lazy { ChaosflixDatabase.getInstance(context) }
|
private val database by lazy { ChaosflixDatabase.getInstance(context) }
|
||||||
private val streamingRepository by lazy { StreamingRepository(apiFactory.streamingApi) }
|
private val streamingRepository by lazy { StreamingRepository(apiFactory.streamingApi) }
|
||||||
private val preferencesManager =
|
private val preferencesManager =
|
||||||
PreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.applicationContext))
|
PreferencesManager(PreferenceManager.getDefaultSharedPreferences(context.applicationContext))
|
||||||
private val offlineItemManager =
|
private val offlineItemManager =
|
||||||
OfflineItemManager(context.applicationContext, database.offlineEventDao(), preferencesManager)
|
OfflineItemManager(
|
||||||
|
context.applicationContext,
|
||||||
|
database.offlineEventDao(),
|
||||||
|
preferencesManager
|
||||||
|
)
|
||||||
private val externalFilesDir = Environment.getExternalStorageDirectory()
|
private val externalFilesDir = Environment.getExternalStorageDirectory()
|
||||||
private val resourcesFacade by lazy { ResourcesFacade(context) }
|
private val resourcesFacade by lazy { ResourcesFacade(context) }
|
||||||
private val mediaRepository by lazy { MediaRepository(apiFactory.recordingApi, database) }
|
private val mediaRepository by lazy { MediaRepository(apiFactory.recordingApi, database) }
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
if (modelClass.isAssignableFrom(BrowseViewModel::class.java)) {
|
return when (modelClass) {
|
||||||
return BrowseViewModel(offlineItemManager, mediaRepository, database, streamingRepository, preferencesManager, resourcesFacade) as T
|
BrowseViewModel::class.java -> BrowseViewModel(
|
||||||
} else if (modelClass.isAssignableFrom(PlayerViewModel::class.java)) {
|
offlineItemManager,
|
||||||
return PlayerViewModel(database) as T
|
mediaRepository,
|
||||||
} else if (modelClass.isAssignableFrom(DetailsViewModel::class.java)) {
|
database,
|
||||||
return DetailsViewModel(database, offlineItemManager, preferencesManager, mediaRepository) as T
|
streamingRepository,
|
||||||
} else if (modelClass.isAssignableFrom(PreferencesViewModel::class.java)) {
|
preferencesManager,
|
||||||
return PreferencesViewModel(mediaRepository, database.watchlistItemDao(), externalFilesDir) as T
|
resourcesFacade) as T
|
||||||
} else if (modelClass.isAssignableFrom(SplashViewModel::class.java)) {
|
PlayerViewModel::class.java -> PlayerViewModel(database) as T
|
||||||
return SplashViewModel(mediaRepository) as T
|
DetailsViewModel::class.java -> DetailsViewModel(
|
||||||
} else {
|
database,
|
||||||
throw UnsupportedOperationException("The requested ViewModel is currently unsupported. " +
|
offlineItemManager,
|
||||||
"Please make sure to implement are correct creation of it. " +
|
preferencesManager,
|
||||||
" Request: ${modelClass.canonicalName}")
|
mediaRepository
|
||||||
|
) as T
|
||||||
|
PreferencesViewModel::class.java -> PreferencesViewModel(
|
||||||
|
mediaRepository,
|
||||||
|
database.watchlistItemDao(),
|
||||||
|
externalFilesDir
|
||||||
|
) as T
|
||||||
|
FavoritesImportViewModel::class.java -> FavoritesImportViewModel(
|
||||||
|
mediaRepository
|
||||||
|
) as T
|
||||||
|
SplashViewModel::class.java -> SplashViewModel(mediaRepository) 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, Context>(::ViewModelFactory)
|
companion object : SingletonHolder<ViewModelFactory, Context>(::ViewModelFactory)
|
||||||
}
|
}
|
||||||
|
|
9
common/src/main/res/drawable/ic_check_box.xml
Normal file
9
common/src/main/res/drawable/ic_check_box.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
||||||
|
</vector>
|
9
common/src/main/res/drawable/ic_check_box_outline.xml
Normal file
9
common/src/main/res/drawable/ic_check_box_outline.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
|
@ -46,7 +46,8 @@
|
||||||
<string name="setting_metered_networks">Allow downloads over metered networks</string>
|
<string name="setting_metered_networks">Allow downloads over metered networks</string>
|
||||||
<string name="settings_choose_recording">Automatically choose recording</string>
|
<string name="settings_choose_recording">Automatically choose recording</string>
|
||||||
<string name="export_favorites">Export Favorites</string>
|
<string name="export_favorites">Export Favorites</string>
|
||||||
<string name="import_favorites">Import Favorites</string>
|
<string name="import_favorites">Import to Favorites</string>
|
||||||
<string name="error_event_not_found">Event not found</string>
|
<string name="error_event_not_found">Event not found</string>
|
||||||
<string name="privacy_policy">Chaosflix does not collect or transmit any personalized data.</string>
|
<string name="privacy_policy">Chaosflix does not collect or transmit any personalized data.</string>
|
||||||
|
<string name="no_recording_found_for">No Recording found for</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String>()
|
||||||
|
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<String>()
|
||||||
|
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\"}" +
|
||||||
|
"]}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,13 @@
|
||||||
android:parentActivityName=".browse.BrowseActivity"/>
|
android:parentActivityName=".browse.BrowseActivity"/>
|
||||||
|
|
||||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||||
|
<activity android:name=".favoritesimport.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>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
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.common.ImportItem
|
||||||
|
import de.nicidienase.chaosflix.touch.databinding.ItemFavoritImportBinding
|
||||||
|
import java.lang.NumberFormatException
|
||||||
|
|
||||||
|
class ImportItemAdapter(private val onListItemClick: ((ImportItem) -> Unit)? = null, private val onLectureClick: ((ImportItem) -> Unit)? = null) : ListAdapter<ImportItem, ImportItemAdapter
|
||||||
|
.ViewHolder>(
|
||||||
|
object : DiffUtil.ItemCallback<ImportItem>() {
|
||||||
|
override fun areItemsTheSame(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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
onListItemClick?.invoke(item)
|
||||||
|
}
|
||||||
|
holder.binding.importItemLecture.setOnClickListener {
|
||||||
|
onLectureClick?.invoke(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import android.widget.Filterable
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
|
||||||
abstract class ItemRecyclerViewAdapter<T, VH : RecyclerView.ViewHolder?>() :
|
abstract class ItemRecyclerViewAdapter<T, VH : RecyclerView.ViewHolder?> :
|
||||||
RecyclerView.Adapter<VH>(), Filterable {
|
RecyclerView.Adapter<VH>(), Filterable {
|
||||||
|
|
||||||
abstract fun getComparator(): Comparator<in T>?
|
abstract fun getComparator(): Comparator<in T>?
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
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() {
|
||||||
|
|
||||||
|
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 = getString(R.string.import_activity_label)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = FavoritesImportActivity::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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.lifecycleOwner = this
|
||||||
|
viewModel = ViewModelProviders.of(requireActivity(), ViewModelFactory.getInstance(requireContext())).get(FavoritesImportViewModel::class.java)
|
||||||
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
|
binding.importList.layoutManager = LinearLayoutManager(context)
|
||||||
|
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
|
||||||
|
|
||||||
|
viewModel.items.observe(viewLifecycleOwner, Observer { events ->
|
||||||
|
if (events != null) {
|
||||||
|
adapter.submitList(events.toList())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.selectAll.observe(viewLifecycleOwner, Observer {
|
||||||
|
activity?.invalidateOptionsMenu()
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.state.observe(viewLifecycleOwner, Observer {
|
||||||
|
when (it.state) {
|
||||||
|
FavoritesImportViewModel.State.IMPORT_DONE -> {
|
||||||
|
// TODO navigate to favorites
|
||||||
|
activity?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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(viewLifecycleOwner, Observer { errorMessage ->
|
||||||
|
if (errorMessage != null) {
|
||||||
|
Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_LONG).apply {
|
||||||
|
view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text).maxLines = 5
|
||||||
|
setAction("OK", View.OnClickListener {
|
||||||
|
this.dismiss()
|
||||||
|
}).show()
|
||||||
|
}
|
||||||
|
viewModel.errorShown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
viewModel.importItemCount.observe(viewLifecycleOwner, Observer { count ->
|
||||||
|
if (count == 0) {
|
||||||
|
binding.buttonImport.hide()
|
||||||
|
} else {
|
||||||
|
binding.buttonImport.show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -119,7 +119,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
importFavorites()
|
importFavorites()
|
||||||
} else {
|
} 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 -> {
|
PERMISSION_REQUEST_EXPORT_FAVORITES -> {
|
||||||
|
|
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.favoritesimport.FavoritesImportFragment"/>
|
||||||
|
</LinearLayout>
|
|
@ -3,7 +3,7 @@
|
||||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar2"
|
android:id="@+id/icon_not_found"
|
||||||
style="?android:attr/progressBarStyle"
|
style="?android:attr/progressBarStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
style="@style/ToolbarStyle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
|
|
|
@ -63,7 +63,6 @@
|
||||||
android:id="@+id/anim_toolbar"
|
android:id="@+id/anim_toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
style="@style/ToolbarStyle"
|
|
||||||
app:titleTextColor="@color/white"
|
app:titleTextColor="@color/white"
|
||||||
app:subtitleTextColor="@color/white"
|
app:subtitleTextColor="@color/white"
|
||||||
app:layout_collapseMode="pin"
|
app:layout_collapseMode="pin"
|
||||||
|
|
52
touch/src/main/res/layout/fragment_favorites_import.xml
Normal file
52
touch/src/main/res/layout/fragment_favorites_import.xml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="de.nicidienase.chaosflix.common.viewmodel.FavoritesImportViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/import_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/button_import"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:backgroundTint="@color/primary"
|
||||||
|
android:text="@string/import_label"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:onClick="@{()->viewModel.importFavorites()}"
|
||||||
|
app:icon="@drawable/ic_bookmark"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/inc_overlay"
|
||||||
|
layout="@layout/loading_overlay"/>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
</layout>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -17,14 +17,14 @@
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/sliding_tabs"
|
android:id="@+id/sliding_tabs"
|
||||||
style="@style/ColoredToolbarStyle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/primary"
|
||||||
app:layout_anchor="@id/toolbar"
|
app:layout_anchor="@id/toolbar"
|
||||||
app:layout_anchorGravity="bottom"
|
app:layout_anchorGravity="bottom"
|
||||||
app:tabIndicatorColor="@color/white"
|
|
||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
app:tabSelectedTextColor="@color/white"
|
app:tabSelectedTextColor="@color/white"
|
||||||
|
app:tabIndicatorColor="@color/white"
|
||||||
app:tabTextColor="@color/inactive_tab_color"/>
|
app:tabTextColor="@color/inactive_tab_color"/>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
148
touch/src/main/res/layout/item_favorit_import.xml
Normal file
148
touch/src/main/res/layout/item_favorit_import.xml
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="item"
|
||||||
|
type="de.nicidienase.chaosflix.common.ImportItem" />
|
||||||
|
<import type="android.view.View"/>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
app:cardCornerRadius="2dp"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/import_item_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/import_item_event"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="@{item.event == null ? View.GONE : View.VISIBLE}"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckBox
|
||||||
|
android:id="@+id/checkBox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:checked="@={item.selected}"
|
||||||
|
android:text=""
|
||||||
|
style="@android:style/Widget.Material.Light.CompoundButton.CheckBox"
|
||||||
|
tools:checked="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/imageView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/imageView" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
imageUrl="@{item.event.thumbUrl}"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:contentDescription="@string/titleimage"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/checkBox"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
app:srcCompat="@drawable/unknown"
|
||||||
|
tools:srcCompat="@drawable/unknown" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="@{item.event.title}"
|
||||||
|
tools:text="Energiespeicher von heute für die Energie von morgen"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/imageView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_subtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@{item.event.subtitle}"
|
||||||
|
tools:text="Wohin eigentlich mit all der erneuerbaren Energie?"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/event_title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/event_title"
|
||||||
|
app:layout_constraintVertical_bias="0.0" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/import_item_lecture"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="@{item.event == null ? View.VISIBLE : View.GONE}"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/icon_not_found"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:src="@drawable/ic_error"
|
||||||
|
android:tint="#E53935"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/not_found_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@{item.lecture.title}"
|
||||||
|
tools:text="Energiespeicher von heute für die Energie von morgen"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/icon_not_found"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/not_found_description" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/not_found_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/no_recording_found_for"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/not_found_title"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</layout>
|
|
@ -3,19 +3,42 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/loading_overlay"
|
android:id="@+id/loading_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/inactive_tab_color"
|
android:background="@color/inactive_tab_color"
|
||||||
|
tools:visibility="visible"
|
||||||
tools:showIn="@layout/fragment_tab_pager_layout">
|
tools:showIn="@layout/fragment_tab_pager_layout">
|
||||||
|
|
||||||
<de.nicidienase.chaosflix.ChaosflixLoadingSpinner
|
<de.nicidienase.chaosflix.ChaosflixLoadingSpinner
|
||||||
|
android:id="@+id/chaosflixLoadingSpinner"
|
||||||
android:layout_width="120dp"
|
android:layout_width="120dp"
|
||||||
android:layout_height="120dp"
|
android:layout_height="120dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/vector_loading_icon"
|
android:src="@drawable/vector_loading_icon"
|
||||||
app:duration="2000"/>
|
app:duration="2000"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</FrameLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressbar"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:indeterminate="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/chaosflixLoadingSpinner"
|
||||||
|
tools:max="10"
|
||||||
|
tools:progress="5"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
style="@style/ColoredToolbarStyle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
|
12
touch/src/main/res/menu/import_menu.xml
Normal file
12
touch/src/main/res/menu/import_menu.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:id="@+id/menu_item_select_all"
|
||||||
|
android:title="@string/select_all"
|
||||||
|
android:icon="@drawable/ic_check_box"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
<item android:id="@+id/menu_item_unselect_all"
|
||||||
|
android:title="Select none"
|
||||||
|
android:icon="@drawable/ic_check_box_outline"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
</menu>
|
|
@ -54,5 +54,9 @@
|
||||||
<string name="play_button">Play-Button</string>
|
<string name="play_button">Play-Button</string>
|
||||||
<string name="list_of_events">List of Events</string>
|
<string name="list_of_events">List of Events</string>
|
||||||
<string name="delete_item">Delete Item</string>
|
<string name="delete_item">Delete Item</string>
|
||||||
|
<string name="import_favorites">Import Favorites</string>
|
||||||
|
<string name="import_activity_label">Fahrplan-Import</string>
|
||||||
|
<string name="select_all">Select all</string>
|
||||||
|
<string name="import_label">Bookmark</string>
|
||||||
<string name="empty" />
|
<string name="empty" />
|
||||||
</resources>
|
</resources>
|
|
@ -6,24 +6,13 @@
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="android:windowContentTransitions">true</item>
|
<item name="android:windowContentTransitions">true</item>
|
||||||
<item name="android:colorPrimary">@color/primary</item>
|
|
||||||
<item name="android:colorPrimaryDark">@color/primary_dark</item>
|
|
||||||
<item name="android:colorAccent">@color/accent</item>
|
|
||||||
<!--<item name="android:textColorPrimary"></item>-->
|
|
||||||
<!--<item name="android:textColorSecondary"></item>-->
|
|
||||||
<!--<item name="android:actionBarStyle">@style/ToolbarStyle</item>-->
|
|
||||||
<!-- <item name="colorControlNormal">@color/white</item>-->
|
|
||||||
<item name="preferenceTheme">@style/SettingsStyle</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ToolbarStyle" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
|
||||||
<item name="colorPrimary">@color/primary</item>
|
<item name="colorPrimary">@color/primary</item>
|
||||||
|
<item name="android:colorPrimary">@color/primary</item>
|
||||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||||
|
<item name="android:colorPrimaryDark">@color/primary_dark</item>
|
||||||
<item name="colorAccent">@color/accent</item>
|
<item name="colorAccent">@color/accent</item>
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:colorAccent">@color/accent</item>
|
||||||
<item name="android:textColorPrimary">@color/white</item>
|
<item name="preferenceTheme">@style/SettingsStyle</item>
|
||||||
<item name="android:textColorSecondary">@color/white</item>
|
|
||||||
<item name="colorControlNormal">@color/white</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SettingsStyle" parent="@style/PreferenceThemeOverlay.v14.Material">
|
<style name="SettingsStyle" parent="@style/PreferenceThemeOverlay.v14.Material">
|
||||||
|
@ -42,10 +31,6 @@
|
||||||
<item name="android:textColorSecondary">@color/white</item>
|
<item name="android:textColorSecondary">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ColoredToolbarStyle" parent="ToolbarStyle">
|
|
||||||
<item name="android:background">@color/primary</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
||||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Add table
Reference in a new issue