play offline file if one exists and other download-related updates

This commit is contained in:
Felix 2017-12-11 22:54:57 +01:00
parent 7aa77b5259
commit 90ceb61c90
21 changed files with 201 additions and 140 deletions

View file

@ -8,7 +8,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.0' classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

View file

@ -0,0 +1,90 @@
package de.nicidienase.chaosflix.touch
import android.app.DownloadManager
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Observer
import android.content.Context
import android.databinding.ObservableField
import de.nicidienase.chaosflix.common.entities.download.OfflineEvent
import io.reactivex.Completable
import io.reactivex.schedulers.Schedulers
import java.io.File
class OfflineItemManager(downloadRefs: List<Long>?) {
val downloadStatus: MutableMap<Long, DownloadStatus>
val downloadManager: DownloadManager
= ChaosflixApplication.APPLICATION_CONTEXT
.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
init {
downloadStatus = HashMap()
downloadRefs?.map { downloadStatus.put(it, DownloadStatus()) }
}
fun updateDownloadStatus(offlineEvents: LiveData<List<OfflineEvent>>) {
Completable.fromAction {
offlineEvents.observeForever(Observer {
if (it != null && it.size > 0) {
val downloadRef = it.map { it.downloadReference }.toTypedArray().toLongArray() ?: longArrayOf()
val cursor = downloadManager.query(DownloadManager.Query().setFilterById(*downloadRef))
if (cursor.moveToFirst()) {
do {
val columnId = cursor.getColumnIndex(DownloadManager.COLUMN_ID)
val id = cursor.getLong(columnId)
val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = cursor.getInt(columnIndex)
val bytesSoFarIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val bytesSoFar = cursor.getInt(bytesSoFarIndex)
val bytesTotalIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val bytesTotal = cursor.getInt(bytesTotalIndex)
val statusText: String =
when (status) {
DownloadManager.STATUS_RUNNING -> "Running"
DownloadManager.STATUS_FAILED -> "Failed"
DownloadManager.STATUS_PAUSED -> "Paused"
DownloadManager.STATUS_SUCCESSFUL -> "Successful"
DownloadManager.STATUS_PENDING -> "Pending"
else -> "UNKNOWN"
}
if (downloadStatus.containsKey(id)) {
val item = downloadStatus[id]
item?.statusText?.set(statusText)
item?.currentBytes?.set(bytesSoFar)
item?.totalBytes?.set(bytesTotal)
} else {
downloadStatus.put(id, DownloadStatus(statusText, bytesSoFar, bytesTotal))
}
} while (cursor.moveToNext())
}
}
})
}.subscribeOn(Schedulers.io()).subscribe()
}
fun deleteOfflineItem(item: OfflineEvent): LiveData<Boolean> {
val result = MutableLiveData<Boolean>()
Completable.fromAction {
val file = File(item.localPath)
if (file.exists()) file.delete()
result.postValue(true)
}.subscribeOn(Schedulers.io()).subscribe()
return result
}
inner class DownloadStatus(status: String = "", currentBytes: Int = 0, totalBytes: Int = 0) {
val statusText: ObservableField<String> = ObservableField()
val currentBytes: ObservableField<Int> = ObservableField()
val totalBytes: ObservableField<Int> = ObservableField()
init {
this.statusText.set(status)
this.currentBytes.set(currentBytes)
this.totalBytes.set(totalBytes)
}
}
}

View file

@ -1,17 +1,13 @@
package de.nicidienase.chaosflix.touch.browse package de.nicidienase.chaosflix.touch.browse
import android.app.DownloadManager
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModel import android.arch.lifecycle.ViewModel
import android.content.Context
import android.databinding.ObservableField
import android.os.Environment
import de.nicidienase.chaosflix.common.entities.ChaosflixDatabase import de.nicidienase.chaosflix.common.entities.ChaosflixDatabase
import de.nicidienase.chaosflix.common.entities.download.OfflineEvent import de.nicidienase.chaosflix.common.entities.download.OfflineEvent
import de.nicidienase.chaosflix.common.network.RecordingService import de.nicidienase.chaosflix.common.network.RecordingService
import de.nicidienase.chaosflix.common.network.StreamingService import de.nicidienase.chaosflix.common.network.StreamingService
import de.nicidienase.chaosflix.touch.ChaosflixApplication import de.nicidienase.chaosflix.touch.OfflineItemManager
import de.nicidienase.chaosflix.touch.sync.Downloader import de.nicidienase.chaosflix.touch.sync.Downloader
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -24,12 +20,14 @@ class BrowseViewModel(
) : ViewModel() { ) : ViewModel() {
val downloader = Downloader(recordingApi, database) val downloader = Downloader(recordingApi, database)
lateinit var offlineItemManager: OfflineItemManager
val downloadManager: DownloadManager init {
= ChaosflixApplication.APPLICATION_CONTEXT getOfflineEvents().observeForever(Observer {
.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val downloadRefs = it?.map { it.downloadReference } ?: emptyList()
offlineItemManager = OfflineItemManager(downloadRefs)
val downloadStatus: MutableMap<Long, DownloadStatus> = HashMap() })
}
fun getConferenceGroups() fun getConferenceGroups()
= database.conferenceGroupDao().getAll() = database.conferenceGroupDao().getAll()
@ -64,77 +62,13 @@ class BrowseViewModel(
fun getRecordingByid(recordingId: Long) = database.recordingDao().findRecordingById(recordingId) fun getRecordingByid(recordingId: Long) = database.recordingDao().findRecordingById(recordingId)
init {
getOfflineEvents().observeForever(Observer {
val downloadRef = it?.map { it.downloadReference }?.map { downloadStatus.put(it, DownloadStatus()) }
})
}
fun updateDownloadStatus() { fun updateDownloadStatus() {
Completable.fromAction { offlineItemManager.updateDownloadStatus(getOfflineEvents())
getOfflineEvents().observeForever(Observer {
if (it != null && it.size > 0) {
val downloadRef = it.map { it.downloadReference }.toTypedArray().toLongArray() ?: longArrayOf()
val cursor = downloadManager.query(DownloadManager.Query().setFilterById(*downloadRef))
if (cursor.moveToFirst()) {
do {
val columnId = cursor.getColumnIndex(DownloadManager.COLUMN_ID)
val id = cursor.getLong(columnId)
val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = cursor.getInt(columnIndex)
val bytesSoFarIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val bytesSoFar = cursor.getInt(bytesSoFarIndex)
val bytesTotalIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val bytesTotal = cursor.getInt(bytesTotalIndex)
val statusText: String =
when (status) {
DownloadManager.STATUS_RUNNING -> "Running"
DownloadManager.STATUS_FAILED -> "Failed"
DownloadManager.STATUS_PAUSED -> "Paused"
DownloadManager.STATUS_SUCCESSFUL -> "Successful"
DownloadManager.STATUS_PENDING -> "Pending"
else -> "UNKNOWN"
}
if (downloadStatus.containsKey(id)) {
val item = downloadStatus[id]
item?.statusText?.set(statusText)
item?.currentBytes?.set(bytesSoFar)
item?.totalBytes?.set(bytesTotal)
} else {
downloadStatus.put(id, DownloadStatus(statusText, bytesSoFar, bytesTotal))
}
} while (cursor.moveToNext())
}
}
})
}.subscribeOn(Schedulers.io()).subscribe()
}
inner class DownloadStatus(status: String = "", currentBytes: Int = 0, totalBytes: Int = 0) {
val statusText: ObservableField<String> = ObservableField()
val currentBytes: ObservableField<Int> = ObservableField()
val totalBytes: ObservableField<Int> = ObservableField()
init {
this.statusText.set(status)
this.currentBytes.set(currentBytes)
this.totalBytes.set(totalBytes)
}
}
fun getUriForEvent(item: OfflineEvent): String {
val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
val uri = "${directory.toURI()}chaosflix/${item.localPath}"
return uri
} }
fun deleteOfflineItem(item: OfflineEvent) { fun deleteOfflineItem(item: OfflineEvent) {
Completable.fromAction { Completable.fromAction {
val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) val file = File(item.localPath)
val file = File("${directory.path}/chaosflix/${item.localPath}")
if (file.exists()) file.delete() if (file.exists()) file.delete()
database.offlineEventDao().deleteById(item.id) database.offlineEventDao().deleteById(item.id)
}.subscribeOn(Schedulers.io()).subscribe() }.subscribeOn(Schedulers.io()).subscribe()

View file

@ -57,6 +57,6 @@ open class EventRecyclerViewAdapter(val listener: OnEventSelectedListener) :
ViewCompat.setTransitionName(holder.mIcon, ViewCompat.setTransitionName(holder.mIcon,
resources.getString(R.string.thumbnail) + event.eventId) resources.getString(R.string.thumbnail) + event.eventId)
holder.mView.setOnClickListener({ v: View -> listener.onEventSelected(items[position]) }) holder.mView.setOnClickListener({ _: View -> listener.onEventSelected(items[position]) })
} }
} }

View file

@ -1,10 +1,6 @@
package de.nicidienase.chaosflix.touch.browse.download package de.nicidienase.chaosflix.touch.browse.download
import android.arch.lifecycle.Observer
import android.databinding.DataBindingUtil import android.databinding.DataBindingUtil
import android.os.Environment
import android.support.design.widget.Snackbar
import android.support.v7.widget.PopupMenu
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -15,7 +11,6 @@ import de.nicidienase.chaosflix.common.entities.download.OfflineEvent
import de.nicidienase.chaosflix.databinding.ItemOfflineEventBinding import de.nicidienase.chaosflix.databinding.ItemOfflineEventBinding
import de.nicidienase.chaosflix.touch.OnEventSelectedListener import de.nicidienase.chaosflix.touch.OnEventSelectedListener
import de.nicidienase.chaosflix.touch.browse.BrowseViewModel import de.nicidienase.chaosflix.touch.browse.BrowseViewModel
import de.nicidienase.chaosflix.touch.playback.PlayerActivity
class OfflineEventAdapter(var items: List<OfflineEvent>, val viewModel: BrowseViewModel, val listener: OnEventSelectedListener) : class OfflineEventAdapter(var items: List<OfflineEvent>, val viewModel: BrowseViewModel, val listener: OnEventSelectedListener) :
RecyclerView.Adapter<OfflineEventAdapter.ViewHolder>() { RecyclerView.Adapter<OfflineEventAdapter.ViewHolder>() {
@ -37,44 +32,17 @@ class OfflineEventAdapter(var items: List<OfflineEvent>, val viewModel: BrowseVi
} }
) )
holder.binding.downloadStatus = viewModel.downloadStatus[item.downloadReference] holder.binding.downloadStatus = viewModel.offlineItemManager.downloadStatus[item.downloadReference]
holder.binding.card.setOnClickListener { view -> holder.binding.buttonDelete.setOnClickListener {
// item.event?.let { viewModel.deleteOfflineItem(item)
// listener.onEventSelected(it) }
// } holder.binding.content.setOnClickListener {
// } item.event?.let {
// listener.onEventSelected(it)
// holder.binding.textViewOptions.setOnClickListener {
// holder.binding.card.setOnClickListener {
val menu = PopupMenu(holder.view.context, holder.binding.card)
menu.inflate(R.menu.offline_menu)
menu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.item_open -> {
item.event?.let {
listener.onEventSelected(it)
}
true
}
R.id.item_play -> {
PlayerActivity.launch(view.context, item.event!!, viewModel.getUriForEvent(item))
true
}
R.id.item_delete -> {
viewModel.deleteOfflineItem(item)
true
}
else -> false
}
} }
menu.show()
} }
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return items.size return items.size
} }

View file

@ -19,6 +19,7 @@ import de.nicidienase.chaosflix.touch.ChaosflixApplication
import de.nicidienase.chaosflix.touch.sync.Downloader import de.nicidienase.chaosflix.touch.sync.Downloader
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.io.File
class DetailsViewModel( class DetailsViewModel(
val database: ChaosflixDatabase, val database: ChaosflixDatabase,
@ -80,7 +81,7 @@ class DetailsViewModel(
Completable.fromAction { Completable.fromAction {
database.offlineEventDao().insert( database.offlineEventDao().insert(
OfflineEvent(eventId = event.eventId, recordingId = recording.recordingId, OfflineEvent(eventId = event.eventId, recordingId = recording.recordingId,
localPath = recording.filename, downloadReference = downloadReference)) localPath = getDownloadDir() + recording.filename, downloadReference = downloadReference))
result.postValue(true) result.postValue(true)
}.subscribeOn(Schedulers.io()).subscribe() }.subscribeOn(Schedulers.io()).subscribe()
} else { } else {
@ -90,8 +91,30 @@ class DetailsViewModel(
return result return result
} }
fun getOfflineItem(eventId: Long) = database.offlineEventDao().getByEventId(eventId)
fun offlineItemExists(eventId: Long): LiveData<Boolean> {
val result = MutableLiveData<Boolean>()
getOfflineItem(eventId).observeForever({ event: OfflineEvent? ->
result.postValue(if (event != null) File(event.localPath).exists() else false)
})
return result
}
fun deleteOfflineItem(item: OfflineEvent) {
Completable.fromAction {
val file = File(item.localPath)
if (file.exists()) file.delete()
database.offlineEventDao().deleteById(item.id)
}.subscribeOn(Schedulers.io()).subscribe()
}
private fun getDownloadDir(): String {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).path + DOWNLOAD_DIR;
}
companion object { companion object {
val DOWNLOAD_DIR = "chaosflix/" val DOWNLOAD_DIR = "/chaosflix/"
val TAG = DetailsViewModel::class.simpleName val TAG = DetailsViewModel::class.simpleName
} }
} }

View file

@ -19,7 +19,6 @@ import de.nicidienase.chaosflix.touch.playback.PlayerActivity
class EventDetailsActivity : AppCompatActivity(), class EventDetailsActivity : AppCompatActivity(),
EventDetailsFragment.OnEventDetailsFragmentInteractionListener, EventDetailsFragment.OnEventDetailsFragmentInteractionListener,
OnEventSelectedListener { OnEventSelectedListener {
override fun onEventSelected(event: PersistentEvent) { override fun onEventSelected(event: PersistentEvent) {
showFragmentForEvent(event.eventId, true) showFragmentForEvent(event.eventId, true)
} }
@ -65,6 +64,10 @@ class EventDetailsActivity : AppCompatActivity(),
PlayerActivity.launch(this, event, recording) PlayerActivity.launch(this, event, recording)
} }
override fun playItem(event: PersistentEvent, uri: String) {
PlayerActivity.launch(this,event,uri)
}
companion object { companion object {
private val EXTRA_EVENT = "extra_event" private val EXTRA_EVENT = "extra_event"

View file

@ -125,16 +125,20 @@ class EventDetailsFragment : Fragment() {
private fun play() { private fun play() {
listener?.let { listener?.let {
val event = this.event viewModel.getOfflineItem(eventId).observe(this, Observer { offlineEvent ->
if (event != null) { if(offlineEvent != null){
Log.d(TAG,"Playing offline file")
viewModel.getRecordingForEvent(eventId) listener?.playItem(event,offlineEvent.localPath)
.observe(this, Observer { persistentRecordings -> } else {
if (persistentRecordings != null) { viewModel.getRecordingForEvent(eventId)
listener!!.playItem(event, Util.getOptimalStream(persistentRecordings)) .observe(this, Observer { persistentRecordings ->
} if (persistentRecordings != null) {
}) Log.d(TAG,"Playing network file")
} listener!!.playItem(event, Util.getOptimalStream(persistentRecordings))
}
})
}
})
} }
} }
@ -166,6 +170,16 @@ class EventDetailsFragment : Fragment() {
menu.findItem(R.id.action_unbookmark).isVisible = false menu.findItem(R.id.action_unbookmark).isVisible = false
} }
menu.findItem(R.id.action_download).isVisible = viewModel.writeExternalStorageAllowed menu.findItem(R.id.action_download).isVisible = viewModel.writeExternalStorageAllowed
viewModel.offlineItemExists(eventId).observe(this,
Observer { itemExists ->
itemExists?.let {
menu.findItem(R.id.action_download).isVisible =
viewModel.writeExternalStorageAllowed && !itemExists
menu.findItem(R.id.action_delete_offline_item).isVisible =
viewModel.writeExternalStorageAllowed && itemExists
}
})
menu.findItem(R.id.action_play).isVisible = appBarExpanded menu.findItem(R.id.action_play).isVisible = appBarExpanded
} }
@ -200,12 +214,21 @@ class EventDetailsFragment : Fragment() {
R.id.action_download -> { R.id.action_download -> {
viewModel.getRecordingForEvent(eventId).observeForever { viewModel.getRecordingForEvent(eventId).observeForever {
viewModel.download(event, Util.getOptimalStream(it!!)).observe(this, Observer { viewModel.download(event, Util.getOptimalStream(it!!)).observe(this, Observer {
if(it != null){ if (it != null) {
val message = if(it) "Download started" else "Error starting download" val message = if (it) "Download started" else "Error starting download"
Snackbar.make(view!!,message, Snackbar.LENGTH_LONG).show() Snackbar.make(view!!, message, Snackbar.LENGTH_LONG).show()
} }
}) })
} }
activity?.invalidateOptionsMenu()
return true
}
R.id.action_delete_offline_item -> {
viewModel.getOfflineItem(eventId).observeForever {
if (it != null) {
viewModel.deleteOfflineItem(it)
}
}
return true return true
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
@ -217,6 +240,7 @@ class EventDetailsFragment : Fragment() {
fun onToolbarStateChange() fun onToolbarStateChange()
fun invalidateOptionsMenu() fun invalidateOptionsMenu()
fun playItem(event: PersistentEvent, recording: PersistentRecording) fun playItem(event: PersistentEvent, recording: PersistentRecording)
fun playItem(event: PersistentEvent, uri: String)
} }
companion object { companion object {

View file

@ -20,10 +20,10 @@ class Downloader(val recordingApi: RecordingService,
private fun updateEverything() { private fun updateEverything() {
updateConferencesAndGroups { conferenceIds -> updateConferencesAndGroups { conferenceIds ->
for (id in conferenceIds) { for (confId in conferenceIds) {
updateEventsForConference(id) { eventIds -> updateEventsForConference(confId) { eventIds ->
for (id in eventIds) { for (eventId in eventIds) {
updateRecordingsForEvent(id) updateRecordingsForEvent(eventId)
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

View file

@ -1,10 +1,11 @@
<?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:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<import type="de.nicidienase.chaosflix.touch.browse.BrowseViewModel.DownloadStatus"/> <import type="de.nicidienase.chaosflix.touch.OfflineItemManager.DownloadStatus"/>
<import type="de.nicidienase.chaosflix.common.entities.recording.persistence.PersistentEvent"/> <import type="de.nicidienase.chaosflix.common.entities.recording.persistence.PersistentEvent"/>
@ -49,6 +50,7 @@
android:scaleType="fitCenter"/> android:scaleType="fitCenter"/>
<LinearLayout <LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
@ -82,11 +84,18 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|right" android:gravity="bottom|right"
android:paddingRight="8dp"
android:text="@{downloadStatus.statusText}" android:text="@{downloadStatus.statusText}"
tools:text="Tag"/> tools:text="Tag"/>
</LinearLayout> </LinearLayout>
<ImageButton
android:id="@+id/button_delete"
android:layout_width="@dimen/del_button_size"
android:layout_height="match_parent"
android:background="@drawable/button_rect_normal"
app:srcCompat="@drawable/ic_delete_dark"/>
</LinearLayout> </LinearLayout>
<ProgressBar <ProgressBar

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto" <menu xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
@ -15,11 +16,17 @@
<item <item
android:id="@+id/action_unbookmark" android:id="@+id/action_unbookmark"
android:icon="@drawable/ic_bookmark" android:icon="@drawable/ic_bookmark"
android:title="Remove Bookmark" android:title="@string/remove_bookmark"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/action_download" android:id="@+id/action_download"
android:title="@string/download" android:title="@string/download"
android:icon="@drawable/ic_download" android:icon="@drawable/ic_download"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_delete_offline_item"
android:title="@string/delete_local_file"
android:icon="@drawable/ic_delete"
android:visible="false"
app:showAsAction="always"/>
</menu> </menu>

View file

@ -6,4 +6,5 @@
<dimen name="margin_content">120dp</dimen> <dimen name="margin_content">120dp</dimen>
<dimen name="details_text_margin">8dp</dimen> <dimen name="details_text_margin">8dp</dimen>
<dimen name="details_title_margin">25dp</dimen> <dimen name="details_title_margin">25dp</dimen>
<dimen name="del_button_size">64dp</dimen>
</resources> </resources>

View file

@ -33,4 +33,6 @@
<string name="drawer_close">Drawer closed</string> <string name="drawer_close">Drawer closed</string>
<string name="about_description">TODO: Description</string> <string name="about_description">TODO: Description</string>
<string name="about_title">Chaosflix</string> <string name="about_title">Chaosflix</string>
<string name="remove_bookmark">Remove Bookmark</string>
<string name="delete_local_file">Delete local File</string>
</resources> </resources>