Merge branch 'develop' into feature/leanbackChannel

This commit is contained in:
Felix 2020-04-11 17:41:32 +02:00
commit d0ed9eaeae
15 changed files with 163 additions and 144 deletions

View file

@ -22,6 +22,7 @@
-ignorewarnings -ignorewarnings
-keep @interface androidx.support.annotation.Keep -keep @interface androidx.support.annotation.Keep
-keep @interface androidx.room.Entity
## Start Retrofit-Rules ## Start Retrofit-Rules
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and

View file

@ -10,5 +10,5 @@ class ProgressEventView(
@Embedded @Embedded
val progress: PlaybackProgress, val progress: PlaybackProgress,
@Relation(entityColumn = "guid", parentColumn = "event_guid") @Relation(entityColumn = "guid", parentColumn = "event_guid")
var event: Event var event: Event?
) )

View file

@ -6,7 +6,7 @@ import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "watchlist_item", @Entity(tableName = "watchlist_item",
indices = arrayOf(Index(value = ["event_guid"], unique = true))) indices = [Index(value = ["event_guid"], unique = true)])
data class WatchlistItem( data class WatchlistItem(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0, var id: Long = 0,

View file

@ -65,7 +65,8 @@ class BrowseViewModel(
getEventsforConference(conference), getEventsforConference(conference),
mediaRepository.updateEventsForConference(conference) mediaRepository.updateEventsForConference(conference)
) { list: List<Event>?, liveEvent: LiveEvent<MediaRepository.State, List<Event>, String>? -> ) { list: List<Event>?, liveEvent: LiveEvent<MediaRepository.State, List<Event>, String>? ->
return@merge LiveEvent(liveEvent?.state ?: MediaRepository.State.DONE, list ?: liveEvent?.data, liveEvent?.error) return@merge LiveEvent(liveEvent?.state ?: MediaRepository.State.DONE, list
?: liveEvent?.data, liveEvent?.error)
} }
fun getBookmarkedEvents(): LiveData<List<Event>> { fun getBookmarkedEvents(): LiveData<List<Event>> {
@ -91,11 +92,12 @@ class BrowseViewModel(
} }
return Transformations.map(dao.getAllWithEvent()) { list -> return Transformations.map(dao.getAllWithEvent()) { list ->
return@map if (filterFinished) { return@map if (filterFinished) {
val result = list.partition { it.progress.progress / 1000 > (it.event.length - 10) } val result = list.partition { it.progress.progress / 1000 + 10 < it.event?.length ?: 0 }
Log.d(TAG, "Filtered ${result.first.size} finished items: ${result.first.map { "${it.progress.progress / 1000}-${it.event.length}|"}}") Log.i(TAG, "Could not load events for ${list.filter { it.event == null }.map{it.progress.eventGuid}}")
result.second.map { it.event.apply { it.event.progress = it.progress.progress } } Log.i(TAG, "Filtered ${result.first.size} finished items: ${result.first.map { "${it.progress.progress / 1000}-${it.event?.length}|" }}")
result.second.mapNotNull { it.event.apply { it.event?.progress = it.progress.progress } }
} else { } else {
list.map { it.event.apply { it.event.progress = it.progress.progress } } list.mapNotNull { it.event?.apply { it.event?.progress = it.progress.progress } }
} }
} }
} }
@ -128,6 +130,7 @@ class BrowseViewModel(
offlineItemManager.deleteOfflineItem(guid) offlineItemManager.deleteOfflineItem(guid)
} }
} }
fun getAutoselectStream() = preferencesManager.autoselectStream fun getAutoselectStream() = preferencesManager.autoselectStream
fun searchEventsPaged(query: String): LiveData<PagedList<Event>> { fun searchEventsPaged(query: String): LiveData<PagedList<Event>> {

View file

@ -8,6 +8,8 @@ import com.google.gson.Gson
import de.nicidienase.chaosflix.common.AnalyticsWrapper import de.nicidienase.chaosflix.common.AnalyticsWrapper
import de.nicidienase.chaosflix.common.AnalyticsWrapperImpl import de.nicidienase.chaosflix.common.AnalyticsWrapperImpl
import de.nicidienase.chaosflix.common.mediadata.MediaRepository import de.nicidienase.chaosflix.common.mediadata.MediaRepository
import de.nicidienase.chaosflix.common.userdata.entities.progress.PlaybackProgress
import de.nicidienase.chaosflix.common.userdata.entities.progress.PlaybackProgressDao
import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItem
import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItemDao import de.nicidienase.chaosflix.common.userdata.entities.watchlist.WatchlistItemDao
import de.nicidienase.chaosflix.common.util.LiveEvent import de.nicidienase.chaosflix.common.util.LiveEvent
@ -22,6 +24,7 @@ import kotlinx.coroutines.launch
class PreferencesViewModel( class PreferencesViewModel(
private val mediaRepository: MediaRepository, private val mediaRepository: MediaRepository,
private val watchlistItemDao: WatchlistItemDao, private val watchlistItemDao: WatchlistItemDao,
private val progressItemDao: PlaybackProgressDao,
private val exportDir: File private val exportDir: File
) : ViewModel() { ) : ViewModel() {
private val gson = Gson() private val gson = Gson()
@ -43,8 +46,18 @@ class PreferencesViewModel(
val favorites = watchlistItemDao.getAllSync() val favorites = watchlistItemDao.getAllSync()
val json = gson.toJson(favorites) val json = gson.toJson(favorites)
Log.d(TAG, json) Log.d(TAG, json)
writeJsonToFile(json, exportDir, FAVORITES_FILENAME)
val progress = progressItemDao.getAllSync()
val progressJson = gson.toJson(progress)
Log.d(TAG, progressJson)
writeJsonToFile(progressJson, exportDir, PROGRESS_FILENAME)
}
}
private fun writeJsonToFile(json: String, exportDir: File, fileName: String) {
if (exportDir.isDirectory) { if (exportDir.isDirectory) {
val file = File("${exportDir.path}${File.separator}$FAVORITES_FILENAME") val file = File("${exportDir.path}${File.separator}$fileName")
if (file.exists()) { if (file.exists()) {
file.delete() file.delete()
file.createNewFile() file.createNewFile()
@ -61,26 +74,41 @@ class PreferencesViewModel(
} }
} }
} }
private fun readJsonFromFile(exportDir: File, fileName: String): String? {
return try {
val file = File("${exportDir.path}${File.separator}$fileName")
if (file.exists()) {
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
bufferedReader.readText()
} else {
null
}
} catch (ex: Exception) {
Log.d(TAG, ex.message, ex)
null
}
} }
fun importFavorites(): MutableLiveData<LiveEvent<State, List<WatchlistItem>, Exception>> { fun importFavorites(): MutableLiveData<LiveEvent<State, List<WatchlistItem>, Exception>> {
val mutableLiveData = MutableLiveData<LiveEvent<State, List<WatchlistItem>, Exception>>() val mutableLiveData = MutableLiveData<LiveEvent<State, List<WatchlistItem>, Exception>>()
mutableLiveData.postValue(LiveEvent(State.Loading, null, null)) mutableLiveData.postValue(LiveEvent(State.Loading, null, null))
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val file = File("${exportDir.path}${File.separator}$FAVORITES_FILENAME") val favoritesJson = readJsonFromFile(exportDir, FAVORITES_FILENAME)
try { val progressJson = readJsonFromFile(exportDir, PROGRESS_FILENAME)
if (file.exists()) { if (favoritesJson == null && progressJson == null) {
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val json: String = bufferedReader.readText()
val fromJson = gson.fromJson(json, Array<WatchlistItem>::class.java)
fromJson.map { watchlistItemDao.saveItem(WatchlistItem(eventGuid = it.eventGuid)) }
mutableLiveData.postValue(LiveEvent(State.Done, fromJson.asList(), null))
} else {
mutableLiveData.postValue(LiveEvent(State.Done, null, null)) mutableLiveData.postValue(LiveEvent(State.Done, null, null))
} else {
favoritesJson?.let {
val fromJson = gson.fromJson(it, Array<WatchlistItem>::class.java)
fromJson.map { watchlistItemDao.saveItem(it) }
} }
} catch (ex: Exception) { progressJson?.let {
mutableLiveData.postValue(LiveEvent(State.Done, null, ex)) val fromJson = gson.fromJson(it, Array<PlaybackProgress>::class.java)
fromJson.map { progressItemDao.saveProgress(it) }
}
mutableLiveData.postValue(LiveEvent(State.Done, error = null))
} }
} }
return mutableLiveData return mutableLiveData
@ -93,5 +121,6 @@ class PreferencesViewModel(
companion object { companion object {
private val TAG = PreferencesViewModel::class.java.simpleName private val TAG = PreferencesViewModel::class.java.simpleName
private const val FAVORITES_FILENAME = "chaosflix_favorites.json" private const val FAVORITES_FILENAME = "chaosflix_favorites.json"
private const val PROGRESS_FILENAME = "chaosflix_progress.json"
} }
} }

View file

@ -53,6 +53,7 @@ class ViewModelFactory private constructor(context: Context) : ViewModelProvider
PreferencesViewModel::class.java -> PreferencesViewModel( PreferencesViewModel::class.java -> PreferencesViewModel(
mediaRepository, mediaRepository,
database.watchlistItemDao(), database.watchlistItemDao(),
database.playbackProgressDao(),
externalFilesDir externalFilesDir
) as T ) as T
FavoritesImportViewModel::class.java -> FavoritesImportViewModel( FavoritesImportViewModel::class.java -> FavoritesImportViewModel(

View file

@ -1,53 +1,106 @@
<resources> <resources>
<string name="api_media_ccc_url" translatable="false">https://api.media.ccc.de</string> <string name="api_media_ccc_url" translatable="false">https://api.media.ccc.de</string>
<string name="streaming_media_ccc_url" translatable="false">https://streaming.media.ccc.de</string> <string name="streaming_media_ccc_url" translatable="false">https://streaming.media.ccc.de</string>
<string name="app_name" translatable="false">Chaosflix</string>
<string name="app_name">Chaosflix</string> <!--Actions-->
<string name="related_movies">Related Videos</string>
<string name="error">Error</string> <string name="error">Error</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="pause">Pause</string> <string name="pause">Pause</string>
<string name="play">Play</string> <string name="play">Play</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="dismiss_error">Dismiss</string>
<string name="start_again">Start again</string>
<string name="resume">Resume</string>
<string name="resume_question">Resume previous position or start from beginning</string>
<string name="return_to_homescreen">Return to Homescreen</string>
<string name="bookmark">Bookmark</string>
<!--Content-->
<string name="related_movies">Related Videos</string>
<string name="random_talks_on_this_track">Random Talks in the same Track</string>
<string name="random_talks">Other random Talks at this Conference</string>
<string name="related_talks">Related Talks</string>
<string name="recordings">Recordings</string>
<string name="conferences">Conferences</string>
<string name="recommendations">Recommendations</string>
<string name="bookmarks">Bookmarks</string>
<!--Watchlist-->
<string name="watchlist_dialog_needed">new-watchlist</string>
<string name="watchlist_message">You can find you watchlist on the Homescreen of Chaosflix</string>
<string name="watchlist">Watchlist</string>
<string name="remove_from_watchlist">Remove from Watchlist</string>
<string name="add_to_watchlist">Add to Watchlist</string>
<!--Error Messages-->
<string name="oops">Oops</string>
<string name="error_event_not_found">Event not found</string>
<string name="no_recording_found_for">No Recording found for</string>
<string name="video_error_media_load_timeout">Media loading timed out</string> <string name="video_error_media_load_timeout">Media loading timed out</string>
<string name="video_error_server_inaccessible">Media server was not reachable</string> <string name="video_error_server_inaccessible">Media server was not reachable</string>
<string name="video_error_unknown_error">Failed to load video</string> <string name="video_error_unknown_error">Failed to load video</string>
<string name="error_fragment_message">An error occurred</string> <string name="error_fragment_message">An error occurred</string>
<string name="dismiss_error">Dismiss</string>
<string name="oops">Oops</string> <!--Livestreams-->
<string name="random_talks_on_this_track">Random Talks in the same Track</string>
<string name="random_talks">Other random Talks at this Conference</string>
<string name="resume_question">Resume previous position or start from beginning</string>
<string name="start_again">Start again</string>
<string name="resume">Resume</string>
<string name="related_talks">Related Talks</string>
<string name="recomendations">Recomendations</string>
<string name="watchlist">Watchlist</string>
<string name="recordings">Recordings</string>
<string name="streaming_prefix">[streaming]</string> <string name="streaming_prefix">[streaming]</string>
<string name="conferences">Conferences</string>
<string name="remove_from_watchlist">Remove from Watchlist</string>
<string name="add_to_watchlist">Add to Watchlist</string>
<string name="streams">Streams</string> <string name="streams">Streams</string>
<string name="livestreams">Livestreams</string> <string name="livestreams">Livestreams</string>
<string name="watchlist_message">You can find you watchlist on the Homescreen of Chaosflix</string>
<!--About-->
<string name="about_chaosflix">About Chaosflix</string>
<string name="about">About</string>
<string name="privacy_policy">Chaosflix does not collect or transmit any personalized data.</string>
<string name="about_description">Chaosflix was developed, because the developer was annoyed
that there was an app for Apple-TV but not Android- and Fire-TV. So now, after the
TV-app there is also one for (android) phones and tablets. Enjoy!</string>
<string name="related_events">Related Events</string>
<string name="about_github">Find the source on Github</string>
<string name="about_beta">Become a Beta-Tester</string>
<string name="about_twitter">Follow the developer on Twitter</string>
<string name="about_playstore">Rate us on Google Play</string>
<string name="about_title">Chaosflix</string>
<string name="about_voctocat"> The Vocotocat-Logo was designed by Sebastian Morr and is released under CC BY-NC-SA 4.0</string>
<string name="chaosflix_licence">Chaosflix is released under MIT-License.</string>
<!--Preferences-->
<string name="watchlist_preferences_key">watchlist</string> <string name="watchlist_preferences_key">watchlist</string>
<string name="watchlist_dialog_needed">new-watchlist</string>
<string name="return_to_homescreen">Return to Homescreen</string>
<string name="clean_cache">Clean Cache</string> <string name="clean_cache">Clean Cache</string>
<string name="download_folder">Download folder</string> <string name="download_folder">Download folder</string>
<string name="pref_autoselect_recording">Don\'t show selection dialog, just use HD-mp4</string> <string name="pref_autoselect_recording">Don\'t show selection dialog, just use HD-mp4</string>
<string name="pref_autoselect_stream">Don\'t show selection dialog, just use DASH</string> <string name="pref_autoselect_stream">Don\'t show selection dialog, just use DASH</string>
<string name="pref_mobile_downloads">Enable downloads over mobile or payed networks</string> <string name="pref_mobile_downloads">Enable downloads over mobile or payed networks</string>
<string name="export_favorites">Export Favorites</string>
<string name="import_favorites">Import to Favorites</string>
<string name="pref_external_player">Always use external player</string> <string name="pref_external_player">Always use external player</string>
<string name="setting_choose_stream">Automatically choose stream</string> <string name="setting_choose_stream">Automatically choose stream</string>
<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="import_favorites">Import to Favorites</string> <!--Moved from touch-->
<string name="error_event_not_found">Event not found</string> <string name="thumbnail">thumbnail</string>
<string name="privacy_policy">Chaosflix does not collect or transmit any personalized data.</string> <string name="search_title">Search</string>
<string name="no_recording_found_for">No Recording found for</string> <string name="download">Download</string>
<string name="downloads">Downloads</string>
<string name="delete">Delete</string>
<string name="titleimage">TitleImage</string>
<string name="search_events">Search Events</string>
<string name="search_talks">Search Talks</string>
<string name="event_details_image_thumbnail_description">Thumbnail-picture for this event</string>
<string name="continue_watching">Continue Watching</string>
<string name="preferences">Preferences</string>
<string name="drawer_open">Drawer open</string>
<string name="drawer_close">Drawer closed</string>
<string name="remove_bookmark">Remove Bookmark</string>
<string name="delete_local_file">Delete local File</string>
<string name="showLibs">Libraries we use</string>
<string name="reload">Reload</string>
<string name="no_livestreams">Currently no livestreams</string>
<string name="share_description">Watch this video from media.ccc.de</string>
<string name="select_stream">Select Stream</string>
<string name="play_button">Play-Button</string>
<string name="list_of_events">List of Events</string>
<string name="delete_item">Delete Item</string>
<string name="import_activity_label">Fahrplan-Import</string>
<string name="select_all">Select all</string>
</resources> </resources>

View file

@ -72,14 +72,14 @@ class ConferencesBrowseFragment : BrowseSupportFragment() {
watchListAdapter = ChaosflixEventAdapter(eventPresenter) watchListAdapter = ChaosflixEventAdapter(eventPresenter)
inProgressAdapter = ChaosflixEventAdapter(eventPresenter) inProgressAdapter = ChaosflixEventAdapter(eventPresenter)
promotedAdapter = ChaosflixEventAdapter(eventPresenter) promotedAdapter = ChaosflixEventAdapter(eventPresenter)
promotedRow = ListRow(HeaderItem(getString(R.string.recommended)), promotedAdapter) promotedRow = ListRow(HeaderItem(getString(R.string.recommendations)), promotedAdapter)
watchlistRow = ListRow(HeaderItem(getString(R.string.watchlist)), watchListAdapter) watchlistRow = ListRow(HeaderItem(getString(R.string.watchlist)), watchListAdapter)
inProgressRow = ListRow(HeaderItem(getString(R.string.continue_watching)), inProgressAdapter) inProgressRow = ListRow(HeaderItem(getString(R.string.continue_watching)), inProgressAdapter)
// Sections and Divider // Sections and Divider
streamingSection = SectionRow(HeaderItem(getString(R.string.livestreams))) streamingSection = SectionRow(HeaderItem(getString(R.string.livestreams)))
streamsDivider = DividerRow() streamsDivider = DividerRow()
recomendationsSections = SectionRow(HeaderItem(getString(R.string.recomendations))) recomendationsSections = SectionRow(HeaderItem(getString(R.string.recommendations)))
recomendationsDivider = DividerRow() recomendationsDivider = DividerRow()
conferencesSection = SectionRow(HeaderItem(getString(R.string.conferences))) conferencesSection = SectionRow(HeaderItem(getString(R.string.conferences)))

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="recommended">Recommended</string>
<string name="continue_watching">Continue Watching</string>
<string name="pref_external_player">Use external player</string>
</resources>

View file

@ -21,7 +21,7 @@ android {
targetSdkVersion rootProject.ext.targetSDK targetSdkVersion rootProject.ext.targetSDK
manifestPlaceholders = [label: appName] manifestPlaceholders = [label: appName]
// odd for touch, even for leanback // odd for touch, even for leanback
versionCode 1 versionCode 298
versionName versionString versionName versionString
if(project.hasProperty("versionCode")){ if(project.hasProperty("versionCode")){

View file

@ -40,7 +40,7 @@ class AboutFragment : Fragment() {
val version = pInfo.versionName val version = pInfo.versionName
val aboutView = AboutPage(requireContext()) val aboutView = AboutPage(requireContext())
.setImage(R.drawable.icon_primary_background) .setImage(R.drawable.icon_primary_background)
.setDescription(resources.getString(R.string.description)) .setDescription(resources.getString(R.string.about_description))
.addItem(Element().setTitle("Version $version")) .addItem(Element().setTitle("Version $version"))
.addWebsite( .addWebsite(
getString(R.string.about_licence_url), getString(R.string.about_licence_url),

View file

@ -367,7 +367,7 @@ class EventDetailsFragment : Fragment() {
val event = viewModel.event.value val event = viewModel.event.value
if (event != null) { if (event != null) {
val shareIntent = Intent(Intent.ACTION_SEND, Uri.parse(event.frontendLink)) val shareIntent = Intent(Intent.ACTION_SEND, Uri.parse(event.frontendLink))
shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.watch_this)) shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_description))
shareIntent.putExtra(Intent.EXTRA_TEXT, event.frontendLink) shareIntent.putExtra(Intent.EXTRA_TEXT, event.frontendLink)
shareIntent.type = "text/plain" shareIntent.type = "text/plain"
startActivity(shareIntent) startActivity(shareIntent)

View file

@ -13,6 +13,6 @@ class SettingsActivity : AppCompatActivity() {
.setContentView<ActivitySettingsBinding>(this, R.layout.activity_settings) .setContentView<ActivitySettingsBinding>(this, R.layout.activity_settings)
setSupportActionBar(binding.toolbarInc.toolbar) setSupportActionBar(binding.toolbarInc.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setTitle(R.string.settings) supportActionBar?.setTitle(R.string.preferences)
} }
} }

View file

@ -34,7 +34,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:backgroundTint="@color/primary" android:backgroundTint="@color/primary"
android:text="@string/import_label" android:text="@string/bookmark"
android:visibility="invisible" android:visibility="invisible"
android:onClick="@{()->viewModel.importFavorites()}" android:onClick="@{()->viewModel.importFavorites()}"
app:icon="@drawable/ic_bookmark" app:icon="@drawable/ic_bookmark"

View file

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Chaosflix</string>
<string name="title">title</string>
<string name="subtitle">subtitle</string>
<string name="thumbnail">thumbnail</string>
<string name="card">card</string>
<string name="select_option">Select Media Option</string>
<string name="search_title">Search</string>
<string name="download">Download</string>
<string name="bookmark">Bookmark</string>
<string name="play">Play</string>
<string name="delete">Delete</string>
<string name="titleimage">TitleImage</string>
<string name="update_database">Update Database</string>
<string name="search_events">Search Events</string>
<string name="search_talks">Search Talks</string>
<string name="event_details_image_thumbnail_description">Thumbnail-picture for this event</string>
<string name="bookmarks">Bookmarks</string>
<string name="recordings">Recordings</string>
<string name="livestreams">Livestreams</string>
<string name="watchlist">Watchlist</string>
<string name="continue_watching">Continue Watching</string>
<string name="about">About</string>
<string name="downloads">Downloads</string>
<string name="preferences">Preferences</string>
<string name="drawer_open">Drawer open</string>
<string name="drawer_close">Drawer closed</string>
<string name="about_title">Chaosflix</string>
<string name="remove_bookmark">Remove Bookmark</string>
<string name="delete_local_file">Delete local File</string>
<string name="about_voctocat"> The Vocotocat-Logo was designed by Sebastian Morr and is released under CC BY-NC-SA 4.0</string>
<string name="showLibs">Libraries we use</string>
<string name="about_chaosflix">About Chaosflix</string>
<string name="chaosflix_licence">Chaosflix is released under MIT-License.</string>
<string name="description">Chaosflix was developed, because the developer was annoyed
that there was an app for Apple-TV but not Android- and Fire-TV. So now, after the
TV-app there is also one for (android) phones and tablets. Enjoy!</string>
<string name="settings">Settings</string>
<string name="reload">Reload</string>
<string name="no_livestreams">Currently no livestreams</string>
<string name="watch_this">Watch this video from media.ccc.de</string>
<string name="related_events">Related Events</string>
<string name="about_github">Find the source on Github</string>
<string name="about_beta">Become a Beta-Tester</string>
<string name="about_twitter">Follow the developer on Twitter</string>
<string name="about_playstore">Rate us on Google Play</string>
<string name="select_stream">Select Stream</string>
<string name="play_button">Play-Button</string>
<string name="list_of_events">List of Events</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" />
</resources>