Merge branch 'master' into patch-5

This commit is contained in:
Allan Nordhøy 2022-08-03 15:29:36 +02:00 committed by GitHub
commit 9be8f5549b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 314 additions and 433 deletions

View File

@ -97,8 +97,6 @@ dependencies {
// Do not update jackson annotations! It does not supports < API 26. // Do not update jackson annotations! It does not supports < API 26.
implementation libs.jacksonAnnotations implementation libs.jacksonAnnotations
implementation libs.mobileffmpeg
coreLibraryDesugaring libs.desugaring coreLibraryDesugaring libs.desugaring
implementation libs.cronet.embedded implementation libs.cronet.embedded
implementation libs.cronet.okhttp implementation libs.cronet.okhttp

View File

@ -193,8 +193,10 @@ class MainActivity : AppCompatActivity() {
}) })
searchView.setOnCloseListener { searchView.setOnCloseListener {
onBackPressed() if (navController.currentDestination?.id == R.id.searchFragment) {
true onBackPressed()
}
false
} }
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }

View File

@ -13,18 +13,15 @@ import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.dialogs.PlaylistOptionsDialog import com.github.libretube.dialogs.PlaylistOptionsDialog
import com.github.libretube.dialogs.VideoOptionsDialog import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.obj.SearchItem import com.github.libretube.obj.SearchItem
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.NavigationHelper import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.github.libretube.util.setWatchProgressLength import com.github.libretube.util.setWatchProgressLength
import com.github.libretube.util.toID import com.github.libretube.util.toID
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException
class SearchAdapter( class SearchAdapter(
private val searchItems: MutableList<SearchItem>, private val searchItems: MutableList<SearchItem>,
@ -128,80 +125,41 @@ class SearchAdapter(
NavigationHelper.navigateChannel(root.context, item.url) NavigationHelper.navigateChannel(root.context, item.url)
} }
val channelId = item.url.toID() val channelId = item.url.toID()
val token = PreferenceHelper.getToken()
// only show subscribe button if logged in isSubscribed(channelId, binding)
if (token != "") isSubscribed(channelId, token, binding)
} }
} }
private fun isSubscribed(channelId: String, token: String, binding: ChannelRowBinding) { private fun isSubscribed(channelId: String, binding: ChannelRowBinding) {
var isSubscribed = false
// check whether the user subscribed to the channel // check whether the user subscribed to the channel
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val response = try { var isSubscribed = SubscriptionHelper.isSubscribed(channelId)
RetrofitInstance.authApi.isSubscribed(
channelId,
token
)
} catch (e: Exception) {
return@launch
}
// if subscribed change text to unsubscribe // if subscribed change text to unsubscribe
if (response.subscribed == true) { if (isSubscribed == true) {
isSubscribed = true
binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe) binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe)
} }
// make sub button visible and set the on click listeners to (un)subscribe // make sub button visible and set the on click listeners to (un)subscribe
if (response.subscribed != null) { if (isSubscribed == null) return@launch
binding.searchSubButton.visibility = View.VISIBLE binding.searchSubButton.visibility = View.VISIBLE
binding.searchSubButton.setOnClickListener { binding.searchSubButton.setOnClickListener {
if (!isSubscribed) { if (isSubscribed == false) {
subscribe(token, channelId) SubscriptionHelper.subscribe(channelId)
binding.searchSubButton.text = binding.searchSubButton.text =
binding.root.context.getString(R.string.unsubscribe) binding.root.context.getString(R.string.unsubscribe)
isSubscribed = true isSubscribed = true
} else { } else {
unsubscribe(token, channelId) SubscriptionHelper.unsubscribe(channelId)
binding.searchSubButton.text = binding.searchSubButton.text =
binding.root.context.getString(R.string.subscribe) binding.root.context.getString(R.string.subscribe)
isSubscribed = false isSubscribed = false
}
} }
} }
} }
} }
private fun subscribe(token: String, channelId: String) {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
return@launch
}
}
}
private fun unsubscribe(token: String, channelId: String) {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: IOException) {
return@launch
}
}
}
private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) { private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
binding.apply { binding.apply {
ConnectionHelper.loadImage(item.thumbnail, searchThumbnail) ConnectionHelper.loadImage(item.thumbnail, searchThumbnail)

View File

@ -1,21 +1,15 @@
package com.github.libretube.adapters package com.github.libretube.adapters
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.databinding.ChannelSubscriptionRowBinding import com.github.libretube.databinding.ChannelSubscriptionRowBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.obj.Subscription import com.github.libretube.obj.Subscription
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.NavigationHelper import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.toID import com.github.libretube.util.toID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) : class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) :
RecyclerView.Adapter<SubscriptionChannelViewHolder>() { RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
@ -46,51 +40,17 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
val channelId = subscription.url.toID() val channelId = subscription.url.toID()
if (subscribed) { if (subscribed) {
subscriptionSubscribe.text = root.context.getString(R.string.subscribe) subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
unsubscribe(channelId) SubscriptionHelper.unsubscribe(channelId)
subscribed = false subscribed = false
} else { } else {
subscriptionSubscribe.text = subscriptionSubscribe.text =
root.context.getString(R.string.unsubscribe) root.context.getString(R.string.unsubscribe)
subscribe(channelId) SubscriptionHelper.subscribe(channelId)
subscribed = true subscribed = true
} }
} }
} }
} }
private fun subscribe(channelId: String) {
fun run() {
CoroutineScope(Dispatchers.IO).launch {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
}
run()
}
private fun unsubscribe(channelId: String) {
fun run() {
CoroutineScope(Dispatchers.IO).launch {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
}
run()
}
} }
class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) : class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) :

View File

@ -4,6 +4,7 @@ import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -41,6 +42,16 @@ class DownloadDialog : DialogFragment() {
binding.title.text = ThemeHelper.getStyledAppName(requireContext()) binding.title.text = ThemeHelper.getStyledAppName(requireContext())
binding.audioRadio.setOnClickListener {
binding.videoSpinner.visibility = View.GONE
binding.audioSpinner.visibility = View.VISIBLE
}
binding.videoRadio.setOnClickListener {
binding.audioSpinner.visibility = View.GONE
binding.videoSpinner.visibility = View.VISIBLE
}
builder.setView(binding.root) builder.setView(binding.root)
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
@ -118,14 +129,15 @@ class DownloadDialog : DialogFragment() {
if (binding.audioSpinner.size >= 1) binding.audioSpinner.setSelection(1) if (binding.audioSpinner.size >= 1) binding.audioSpinner.setSelection(1)
binding.download.setOnClickListener { binding.download.setOnClickListener {
val selectedAudioUrl = audioUrl[binding.audioSpinner.selectedItemPosition] val selectedAudioUrl =
val selectedVideoUrl = vidUrl[binding.videoSpinner.selectedItemPosition] if (binding.audioRadio.isChecked) audioUrl[binding.audioSpinner.selectedItemPosition] else ""
val selectedVideoUrl =
if (binding.videoRadio.isChecked) vidUrl[binding.videoSpinner.selectedItemPosition] else ""
val intent = Intent(context, DownloadService::class.java) val intent = Intent(context, DownloadService::class.java)
intent.putExtra("videoId", videoId) intent.putExtra("videoName", streams.title)
intent.putExtra("videoUrl", selectedVideoUrl) intent.putExtra("videoUrl", selectedVideoUrl)
intent.putExtra("audioUrl", selectedAudioUrl) intent.putExtra("audioUrl", selectedAudioUrl)
intent.putExtra("duration", duration)
context?.startService(intent) context?.startService(intent)
dismiss() dismiss()
} }

View File

@ -13,7 +13,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ShareDialog( class ShareDialog(
private val id: String, private val id: String,
private val isPlaylist: Boolean private val isPlaylist: Boolean,
private val position: Long = 0L
) : DialogFragment() { ) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -39,7 +40,14 @@ class ShareDialog(
else -> instanceUrl else -> instanceUrl
} }
val path = if (!isPlaylist) "/watch?v=$id" else "/playlist?list=$id" val path = if (!isPlaylist) "/watch?v=$id" else "/playlist?list=$id"
val url = "$host$path" var url = "$host$path"
if (PreferenceHelper.getBoolean(
PreferenceKeys.SHARE_WITH_TIME_CODE,
true
)
) {
url += "?t=$position"
}
val intent = Intent() val intent = Intent()
intent.apply { intent.apply {

View File

@ -5,17 +5,15 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.ChannelAdapter import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.databinding.FragmentChannelBinding import com.github.libretube.databinding.FragmentChannelBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.github.libretube.util.toID import com.github.libretube.util.toID
import retrofit2.HttpException import retrofit2.HttpException
@ -31,7 +29,7 @@ class ChannelFragment : Fragment() {
var nextPage: String? = null var nextPage: String? = null
private var channelAdapter: ChannelAdapter? = null private var channelAdapter: ChannelAdapter? = null
private var isLoading = true private var isLoading = true
private var isSubscribed: Boolean = false private var isSubscribed: Boolean? = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -61,13 +59,7 @@ class ChannelFragment : Fragment() {
val refreshChannel = { val refreshChannel = {
binding.channelRefresh.isRefreshing = true binding.channelRefresh.isRefreshing = true
fetchChannel() fetchChannel()
if (PreferenceHelper.getToken() != "") { isSubscribed()
isSubscribed()
} else {
binding.channelSubscribe.setOnClickListener {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
} }
refreshChannel() refreshChannel()
binding.channelRefresh.setOnRefreshListener { binding.channelRefresh.setOnRefreshListener {
@ -90,76 +82,28 @@ class ChannelFragment : Fragment() {
} }
private fun isSubscribed() { private fun isSubscribed() {
fun run() { lifecycleScope.launchWhenCreated {
lifecycleScope.launchWhenCreated { isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
val response = try { if (isSubscribed == null) return@launchWhenCreated
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.isSubscribed( runOnUiThread {
channelId!!, if (isSubscribed == true) {
token binding.channelSubscribe.text = getString(R.string.unsubscribe)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
return@launchWhenCreated
} }
runOnUiThread { binding.channelSubscribe.setOnClickListener {
if (response.subscribed == true) { binding.channelSubscribe.text = if (isSubscribed == true) {
SubscriptionHelper.unsubscribe(channelId!!)
isSubscribed = false
getString(R.string.subscribe)
} else {
SubscriptionHelper.subscribe(channelId!!)
isSubscribed = true isSubscribed = true
binding.channelSubscribe.text = getString(R.string.unsubscribe) getString(R.string.unsubscribe)
}
binding.channelSubscribe.setOnClickListener {
if (response.subscribed != null) {
binding.channelSubscribe.text = if (isSubscribed) {
unsubscribe()
getString(R.string.subscribe)
} else {
subscribe()
getString(R.string.unsubscribe)
}
}
} }
} }
} }
} }
run()
}
private fun subscribe() {
fun run() {
lifecycleScope.launchWhenCreated {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
isSubscribed = true
}
}
run()
}
private fun unsubscribe() {
fun run() {
lifecycleScope.launchWhenCreated {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
isSubscribed = false
}
}
run()
} }
private fun fetchChannel() { private fun fetchChannel() {

View File

@ -53,9 +53,7 @@ import com.github.libretube.obj.ChapterSegment
import com.github.libretube.obj.Playlist import com.github.libretube.obj.Playlist
import com.github.libretube.obj.Segment import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments import com.github.libretube.obj.Segments
import com.github.libretube.obj.StreamItem
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.services.BackgroundMode import com.github.libretube.services.BackgroundMode
@ -66,6 +64,7 @@ import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.OnDoubleTapEventListener import com.github.libretube.util.OnDoubleTapEventListener
import com.github.libretube.util.PlayerHelper import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.github.libretube.util.hideKeyboard import com.github.libretube.util.hideKeyboard
import com.github.libretube.util.toID import com.github.libretube.util.toID
@ -117,8 +116,9 @@ class PlayerFragment : Fragment() {
private var videoId: String? = null private var videoId: String? = null
private var playlistId: String? = null private var playlistId: String? = null
private var channelId: String? = null private var channelId: String? = null
private var isSubscribed: Boolean = false private var isSubscribed: Boolean? = false
private var isLive = false private var isLive = false
private lateinit var streams: Streams
/** /**
* for the transition * for the transition
@ -175,7 +175,6 @@ class PlayerFragment : Fragment() {
/** /**
* for autoplay * for autoplay
*/ */
private var relatedStreams: List<StreamItem>? = arrayListOf()
private var nextStreamId: String? = null private var nextStreamId: String? = null
private var playlistStreamIds: MutableList<String> = arrayListOf() private var playlistStreamIds: MutableList<String> = arrayListOf()
private var playlistNextPage: String? = null private var playlistNextPage: String? = null
@ -187,13 +186,6 @@ class PlayerFragment : Fragment() {
private lateinit var mediaSessionConnector: MediaSessionConnector private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var playerNotification: PlayerNotificationManager private lateinit var playerNotification: PlayerNotificationManager
/**
* for the media description of the notification
*/
private lateinit var title: String
private lateinit var uploader: String
private lateinit var thumbnailUrl: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -540,7 +532,7 @@ class PlayerFragment : Fragment() {
// share button // share button
binding.relPlayerShare.setOnClickListener { binding.relPlayerShare.setOnClickListener {
val shareDialog = ShareDialog(videoId!!, false) val shareDialog = ShareDialog(videoId!!, false, exoPlayer.currentPosition)
shareDialog.show(childFragmentManager, "ShareDialog") shareDialog.show(childFragmentManager, "ShareDialog")
} }
@ -739,7 +731,7 @@ class PlayerFragment : Fragment() {
private fun playVideo() { private fun playVideo() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { streams = try {
RetrofitInstance.api.getStreams(videoId!!) RetrofitInstance.api.getStreams(videoId!!)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
@ -751,21 +743,13 @@ class PlayerFragment : Fragment() {
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} }
// for the notification description adapter
title = response.title!!
uploader = response.uploader!!
thumbnailUrl = response.thumbnailUrl!!
channelId = response.uploaderUrl.toID()
// save related streams for autoplay
relatedStreams = response.relatedStreams
runOnUiThread { runOnUiThread {
// set media sources for the player // set media sources for the player
setResolutionAndSubtitles(response) setResolutionAndSubtitles(streams)
prepareExoPlayerView() prepareExoPlayerView()
initializePlayerView(response) initializePlayerView(streams)
seekToWatchPosition() if (!isLive) seekToWatchPosition()
exoPlayer.prepare() exoPlayer.prepare()
exoPlayer.play() exoPlayer.play()
exoPlayerView.useController = true exoPlayerView.useController = true
@ -776,7 +760,7 @@ class PlayerFragment : Fragment() {
// prepare for autoplay // prepare for autoplay
initAutoPlay() initAutoPlay()
if (watchHistoryEnabled) { if (watchHistoryEnabled) {
PreferenceHelper.addToWatchHistory(videoId!!, response) PreferenceHelper.addToWatchHistory(videoId!!, streams)
} }
} }
} }
@ -828,7 +812,10 @@ class PlayerFragment : Fragment() {
val watchPositions = PreferenceHelper.getWatchPositions() val watchPositions = PreferenceHelper.getWatchPositions()
var position: Long? = null var position: Long? = null
watchPositions.forEach { watchPositions.forEach {
if (it.videoId == videoId) position = it.position if (it.videoId == videoId &&
// don't seek to the position if it's the end, autoplay would skip it immediately
streams.duration!! - it.position / 1000 > 2
) position = it.position
} }
// support for time stamped links // support for time stamped links
val timeStamp: Long? = arguments?.getLong("timeStamp") val timeStamp: Long? = arguments?.getLong("timeStamp")
@ -884,9 +871,9 @@ class PlayerFragment : Fragment() {
// else: the video must be the last video of the playlist so nothing happens // else: the video must be the last video of the playlist so nothing happens
// if it's not a playlist then use the next related video // if it's not a playlist then use the next related video
} else if (relatedStreams != null && relatedStreams!!.isNotEmpty()) { } else if (streams.relatedStreams != null && streams.relatedStreams!!.isNotEmpty()) {
// save next video from related streams for autoplay // save next video from related streams for autoplay
nextStreamId = relatedStreams!![0].url.toID() nextStreamId = streams.relatedStreams!![0].url.toID()
} }
} }
} }
@ -896,6 +883,9 @@ class PlayerFragment : Fragment() {
// check whether there is a new video in the queue // check whether there is a new video in the queue
// by making sure that the next and the current video aren't the same // by making sure that the next and the current video aren't the same
saveWatchPosition() saveWatchPosition()
// forces the comments to reload for the new video
commentsLoaded = false
binding.commentsRecView.adapter = null
if (videoId != nextStreamId) { if (videoId != nextStreamId) {
// save the id of the next stream as videoId and load the next video // save the id of the next stream as videoId and load the next video
videoId = nextStreamId videoId = nextStreamId
@ -1064,9 +1054,9 @@ class PlayerFragment : Fragment() {
intent.action = Intent.ACTION_VIEW intent.action = Intent.ACTION_VIEW
intent.setDataAndType(uri, "video/*") intent.setDataAndType(uri, "video/*")
intent.putExtra(Intent.EXTRA_TITLE, title) intent.putExtra(Intent.EXTRA_TITLE, streams.title)
intent.putExtra("title", title) intent.putExtra("title", streams.title)
intent.putExtra("artist", uploader) intent.putExtra("artist", streams.uploader)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try { try {
@ -1278,7 +1268,7 @@ class PlayerFragment : Fragment() {
// get the name of the currently played chapter // get the name of the currently played chapter
private fun getCurrentChapterIndex(): Int { private fun getCurrentChapterIndex(): Int {
val currentPosition = exoPlayer.currentPosition val currentPosition = exoPlayer.currentPosition
var chapterIndex: Int? = null var chapterIndex = 0
chapters.forEachIndexed { index, chapter -> chapters.forEachIndexed { index, chapter ->
// check whether the chapter start is greater than the current player position // check whether the chapter start is greater than the current player position
@ -1287,7 +1277,7 @@ class PlayerFragment : Fragment() {
chapterIndex = index chapterIndex = index
} }
} }
return chapterIndex!! return chapterIndex
} }
private fun setMediaSource( private fun setMediaSource(
@ -1525,7 +1515,12 @@ class PlayerFragment : Fragment() {
playerNotification = PlayerNotificationManager playerNotification = PlayerNotificationManager
.Builder(c, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID) .Builder(c, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID)
.setMediaDescriptionAdapter( .setMediaDescriptionAdapter(
DescriptionAdapter(title, uploader, thumbnailUrl, requireContext()) DescriptionAdapter(
streams.title!!,
streams.uploader!!,
streams.thumbnailUrl!!,
requireContext()
)
) )
.build() .build()
@ -1564,38 +1559,22 @@ class PlayerFragment : Fragment() {
private fun isSubscribed() { private fun isSubscribed() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
RetrofitInstance.authApi.isSubscribed(
channelId!!, if (isSubscribed == null) return@launchWhenCreated
token
)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
}
runOnUiThread { runOnUiThread {
if (response.subscribed == true) { if (isSubscribed == true) {
isSubscribed = true
binding.playerSubscribe.text = getString(R.string.unsubscribe) binding.playerSubscribe.text = getString(R.string.unsubscribe)
} }
if (response.subscribed != null) { binding.playerSubscribe.setOnClickListener {
binding.playerSubscribe.setOnClickListener { if (isSubscribed == true) {
if (isSubscribed) { SubscriptionHelper.unsubscribe(channelId!!)
unsubscribe(channelId!!) binding.playerSubscribe.text = getString(R.string.subscribe)
binding.playerSubscribe.text = getString(R.string.subscribe) } else {
} else { SubscriptionHelper.subscribe(channelId!!)
subscribe(channelId!!) binding.playerSubscribe.text = getString(R.string.unsubscribe)
binding.playerSubscribe.text = getString(R.string.unsubscribe)
}
} }
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT)
.show()
} }
} }
} }
@ -1603,50 +1582,6 @@ class PlayerFragment : Fragment() {
run() run()
} }
private fun subscribe(channelId: String) {
fun run() {
lifecycleScope.launchWhenCreated {
try {
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response$e")
return@launchWhenCreated
}
isSubscribed = true
}
}
run()
}
private fun unsubscribe(channel_id: String) {
fun run() {
lifecycleScope.launchWhenCreated {
try {
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channel_id)
)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
}
isSubscribed = false
}
}
run()
}
private fun Fragment?.runOnUiThread(action: () -> Unit) { private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return this ?: return
if (!isAdded) return // Fragment not attached to an Activity if (!isAdded) return // Fragment not attached to an Activity

View File

@ -19,6 +19,7 @@ import com.github.libretube.obj.StreamItem
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.toID import com.github.libretube.util.toID
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException import retrofit2.HttpException
@ -53,63 +54,57 @@ class SubscriptionsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
token = PreferenceHelper.getToken() token = PreferenceHelper.getToken()
if (token != "") { binding.subRefresh.isEnabled = true
binding.loginOrRegister.visibility = View.GONE
binding.subRefresh.isEnabled = true
binding.subProgress.visibility = View.VISIBLE binding.subProgress.visibility = View.VISIBLE
val grid = PreferenceHelper.getString( val grid = PreferenceHelper.getString(
PreferenceKeys.GRID_COLUMNS, PreferenceKeys.GRID_COLUMNS,
resources.getInteger(R.integer.grid_items).toString() resources.getInteger(R.integer.grid_items).toString()
) )
binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt()) binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed()
binding.subRefresh.setOnRefreshListener {
fetchChannels()
fetchFeed() fetchFeed()
binding.subRefresh.setOnRefreshListener {
fetchChannels()
fetchFeed()
}
binding.sortTV.setOnClickListener {
showSortDialog()
}
binding.toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false
binding.toggleSubs.setOnClickListener {
if (!binding.subChannelsContainer.isVisible) {
if (!loadedSubbedChannels) {
binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels()
loadedSubbedChannels = true
}
binding.subChannelsContainer.visibility = View.VISIBLE
binding.subFeedContainer.visibility = View.GONE
} else {
binding.subChannelsContainer.visibility = View.GONE
binding.subFeedContainer.visibility = View.VISIBLE
}
}
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
// scroll view is at bottom
if (isLoaded) {
binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
}
}
} else {
binding.subRefresh.isEnabled = false
binding.subFeedContainer.visibility = View.GONE
} }
binding.sortTV.setOnClickListener {
showSortDialog()
}
binding.toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false
binding.toggleSubs.setOnClickListener {
if (!binding.subChannelsContainer.isVisible) {
if (!loadedSubbedChannels) {
binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels()
loadedSubbedChannels = true
}
binding.subChannelsContainer.visibility = View.VISIBLE
binding.subFeedContainer.visibility = View.GONE
} else {
binding.subChannelsContainer.visibility = View.GONE
binding.subFeedContainer.visibility = View.VISIBLE
}
}
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
// scroll view is at bottom
if (isLoaded) {
binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
}
}
} }
private fun showSortDialog() { private fun showSortDialog() {
@ -130,7 +125,10 @@ class SubscriptionsFragment : Fragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
feed = try { feed = try {
RetrofitInstance.authApi.getFeed(token) if (token != "") RetrofitInstance.authApi.getFeed(token)
else RetrofitInstance.authApi.getUnauthenticatedFeed(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
@ -148,15 +146,7 @@ class SubscriptionsFragment : Fragment() {
showFeed() showFeed()
} else { } else {
runOnUiThread { runOnUiThread {
with(binding.boogh) { binding.emptyFeed.visibility = View.VISIBLE
visibility = View.VISIBLE
setImageResource(R.drawable.ic_list)
}
with(binding.textLike) {
visibility = View.VISIBLE
text = getString(R.string.emptyList)
}
binding.loginOrRegister.visibility = View.VISIBLE
} }
} }
binding.subProgress.visibility = View.GONE binding.subProgress.visibility = View.GONE
@ -185,7 +175,10 @@ class SubscriptionsFragment : Fragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.authApi.subscriptions(token) if (token != "") RetrofitInstance.authApi.subscriptions(token)
else RetrofitInstance.authApi.unauthenticatedSubscriptions(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")

View File

@ -235,6 +235,21 @@ object PreferenceHelper {
return getString(PreferenceKeys.ERROR_LOG, "") return getString(PreferenceKeys.ERROR_LOG, "")
} }
fun getLocalSubscriptions(): List<String> {
val json = settings.getString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, "")
return try {
val type = object : TypeReference<List<String>>() {}
mapper.readValue(json, type)
} catch (e: Exception) {
listOf()
}
}
fun setLocalSubscriptions(channels: List<String>) {
val json = mapper.writeValueAsString(channels)
editor.putString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, json).commit()
}
private fun getDefaultSharedPreferences(context: Context): SharedPreferences { private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context) return PreferenceManager.getDefaultSharedPreferences(context)
} }

View File

@ -61,7 +61,6 @@ object PreferenceKeys {
/** /**
* Download * Download
*/ */
const val DOWNLOAD_VIDEO_FORMAT = "video_format"
const val DOWNLOAD_LOCATION = "download_location" const val DOWNLOAD_LOCATION = "download_location"
const val DOWNLOAD_FOLDER = "download_folder" const val DOWNLOAD_FOLDER = "download_folder"
@ -81,9 +80,15 @@ object PreferenceKeys {
const val CLEAR_SEARCH_HISTORY = "clear_search_history" const val CLEAR_SEARCH_HISTORY = "clear_search_history"
const val CLEAR_WATCH_HISTORY = "clear_watch_history" const val CLEAR_WATCH_HISTORY = "clear_watch_history"
const val CLEAR_WATCH_POSITIONS = "clear_watch_positions" const val CLEAR_WATCH_POSITIONS = "clear_watch_positions"
const val SHARE_WITH_TIME_CODE = "share_with_time_code"
/** /**
* Error logs * Error logs
*/ */
const val ERROR_LOG = "error_log" const val ERROR_LOG = "error_log"
/**
* Data
*/
const val LOCAL_SUBSCRIPTIONS = "local_subscriptions"
} }

View File

@ -17,7 +17,6 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit
import com.github.libretube.DOWNLOAD_CHANNEL_ID import com.github.libretube.DOWNLOAD_CHANNEL_ID
import com.github.libretube.DOWNLOAD_FAILURE_NOTIFICATION_ID import com.github.libretube.DOWNLOAD_FAILURE_NOTIFICATION_ID
import com.github.libretube.DOWNLOAD_PENDING_NOTIFICATION_ID import com.github.libretube.DOWNLOAD_PENDING_NOTIFICATION_ID
@ -35,11 +34,9 @@ class DownloadService : Service() {
private lateinit var notification: NotificationCompat.Builder private lateinit var notification: NotificationCompat.Builder
private var downloadId: Long = -1 private var downloadId: Long = -1
private lateinit var videoId: String private lateinit var videoName: String
private lateinit var videoUrl: String private lateinit var videoUrl: String
private lateinit var audioUrl: String private lateinit var audioUrl: String
private lateinit var extension: String
private var duration: Int = 0
private var downloadType: Int = 3 private var downloadType: Int = 3
private lateinit var audioDir: File private lateinit var audioDir: File
@ -52,13 +49,11 @@ class DownloadService : Service() {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
videoId = intent?.getStringExtra("videoId")!! videoName = intent?.getStringExtra("videoName")!!
videoUrl = intent.getStringExtra("videoUrl")!! videoUrl = intent.getStringExtra("videoUrl")!!
audioUrl = intent.getStringExtra("audioUrl")!! audioUrl = intent.getStringExtra("audioUrl")!!
duration = intent.getIntExtra("duration", 1)
extension = PreferenceHelper.getString(PreferenceKeys.DOWNLOAD_VIDEO_FORMAT, ".mp4")!! downloadType = if (audioUrl != "") DownloadType.AUDIO
downloadType = if (audioUrl != "" && videoUrl != "") DownloadType.MUX
else if (audioUrl != "") DownloadType.AUDIO
else if (videoUrl != "") DownloadType.VIDEO else if (videoUrl != "") DownloadType.VIDEO
else DownloadType.NONE else DownloadType.NONE
if (downloadType != DownloadType.NONE) { if (downloadType != DownloadType.NONE) {
@ -115,18 +110,8 @@ class DownloadService : Service() {
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
) )
when (downloadType) { when (downloadType) {
DownloadType.MUX -> {
audioDir = File(tempDir, "$videoId-audio")
videoDir = File(tempDir, "$videoId-video")
downloadId = downloadManagerRequest(
getString(R.string.video),
getString(R.string.downloading),
videoUrl,
videoDir
)
}
DownloadType.VIDEO -> { DownloadType.VIDEO -> {
videoDir = File(libretubeDir, "$videoId-video") videoDir = File(libretubeDir, videoName)
downloadId = downloadManagerRequest( downloadId = downloadManagerRequest(
getString(R.string.video), getString(R.string.video),
getString(R.string.downloading), getString(R.string.downloading),
@ -135,7 +120,7 @@ class DownloadService : Service() {
) )
} }
DownloadType.AUDIO -> { DownloadType.AUDIO -> {
audioDir = File(libretubeDir, "$videoId-audio") audioDir = File(libretubeDir, videoName)
downloadId = downloadManagerRequest( downloadId = downloadManagerRequest(
getString(R.string.audio), getString(R.string.audio),
getString(R.string.downloading), getString(R.string.downloading),
@ -146,6 +131,7 @@ class DownloadService : Service() {
} }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
Log.e(TAG, "download error $e") Log.e(TAG, "download error $e")
downloadFailedNotification()
} }
} }
@ -166,11 +152,6 @@ class DownloadService : Service() {
downloadSucceededNotification() downloadSucceededNotification()
onDestroy() onDestroy()
} }
} else {
try {
muxDownloadedMedia()
} catch (e: Exception) {
}
} }
} }
} }
@ -233,7 +214,7 @@ class DownloadService : Service() {
val builder = NotificationCompat.Builder(this@DownloadService, DOWNLOAD_CHANNEL_ID) val builder = NotificationCompat.Builder(this@DownloadService, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_download) .setSmallIcon(R.drawable.ic_download)
.setContentTitle(resources.getString(R.string.success)) .setContentTitle(resources.getString(R.string.success))
.setContentText(getString(R.string.fail)) .setContentText(getString(R.string.downloadsucceeded))
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
with(NotificationManagerCompat.from(this@DownloadService)) { with(NotificationManagerCompat.from(this@DownloadService)) {
// notificationId is a unique int for each notification that you must define // notificationId is a unique int for each notification that you must define
@ -241,39 +222,6 @@ class DownloadService : Service() {
} }
} }
private fun muxDownloadedMedia() {
val command = "-y -i $videoDir -i $audioDir -c copy $libretubeDir/${videoId}$extension"
notification.setContentTitle("Muxing")
FFmpegKit.executeAsync(
command,
{ session ->
val state = session.state
val returnCode = session.returnCode
// CALLED WHEN SESSION IS EXECUTED
Log.d(
TAG,
String.format(
"FFmpeg process exited with state %s and rc %s.%s",
state,
returnCode,
session.failStackTrace
)
)
tempDir.deleteRecursively()
if (returnCode.toString() != "0") downloadFailedNotification()
else downloadSucceededNotification()
onDestroy()
},
{
// CALLED WHEN SESSION PRINTS LOGS
Log.e(TAG, it.message.toString())
}
) {
// CALLED WHEN SESSION GENERATES STATISTICS
Log.e(TAG + "stat", it.time.toString())
}
}
override fun onDestroy() { override fun onDestroy() {
try { try {
unregisterReceiver(onDownloadComplete) unregisterReceiver(onDownloadComplete)

View File

@ -99,6 +99,9 @@ interface PipedApi {
@GET("feed") @GET("feed")
suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem> suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem>
@GET("feed/unauthenticated")
suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List<StreamItem>
@GET("subscribed") @GET("subscribed")
suspend fun isSubscribed( suspend fun isSubscribed(
@Query("channelId") channelId: String, @Query("channelId") channelId: String,
@ -108,6 +111,9 @@ interface PipedApi {
@GET("subscriptions") @GET("subscriptions")
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription> suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>
@GET("subscriptions/unauthenticated")
suspend fun unauthenticatedSubscriptions(@Query("channels") channels: String): List<Subscription>
@POST("subscribe") @POST("subscribe")
suspend fun subscribe( suspend fun subscribe(
@Header("Authorization") token: String, @Header("Authorization") token: String,

View File

@ -0,0 +1,71 @@
package com.github.libretube.util
import android.util.Log
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object SubscriptionHelper {
val TAG = "SubscriptionHelper"
fun subscribe(channelId: String) {
if (PreferenceHelper.getToken() != "") {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.subscribe(
PreferenceHelper.getToken(),
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
} else {
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
channels.add(channelId)
PreferenceHelper.setLocalSubscriptions(channels)
}
}
fun unsubscribe(channelId: String) {
if (PreferenceHelper.getToken() != "") {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.unsubscribe(
PreferenceHelper.getToken(),
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
} else {
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
channels.remove(channelId)
PreferenceHelper.setLocalSubscriptions(channels)
}
}
suspend fun isSubscribed(channelId: String): Boolean? {
if (PreferenceHelper.getToken() != "") {
val isSubscribed = try {
RetrofitInstance.authApi.isSubscribed(
channelId,
PreferenceHelper.getToken()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
return null
}
return isSubscribed.subscribed
} else {
return PreferenceHelper.getLocalSubscriptions().contains(channelId)
}
}
fun getFormattedLocalSubscriptions(): String {
return PreferenceHelper.getLocalSubscriptions().joinToString(",")
}
}

View File

@ -4,7 +4,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -14,6 +13,28 @@
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="20sp" /> android:textSize="20sp" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:checkedButton="@id/video_radio"
android:orientation="horizontal">
<RadioButton
android:id="@+id/video_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video" />
<RadioButton
android:id="@+id/audio_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/audio" />
</RadioGroup>
<Spinner <Spinner
android:id="@+id/video_spinner" android:id="@+id/video_spinner"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -24,12 +45,13 @@
android:id="@+id/audio_spinner" android:id="@+id/audio_spinner"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" /> android:layout_margin="8dp"
android:visibility="gone" />
<Button <Button
android:id="@+id/download" android:id="@+id/download"
style="@style/CustomDialogButton" style="@style/CustomDialogButton"
android:layout_marginRight="16dp" android:layout_marginEnd="16dp"
android:text="@string/download" /> android:text="@string/download" />
</LinearLayout> </LinearLayout>

View File

@ -15,10 +15,11 @@
android:visibility="gone" /> android:visibility="gone" />
<RelativeLayout <RelativeLayout
android:id="@+id/loginOrRegister" android:id="@+id/emptyFeed"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true"> android:layout_centerInParent="true"
android:visibility="gone">
<ImageView <ImageView
android:id="@+id/boogh" android:id="@+id/boogh"
@ -26,7 +27,7 @@
android:layout_height="100dp" android:layout_height="100dp"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:src="@drawable/ic_login" /> android:src="@drawable/ic_list" />
<TextView <TextView
android:id="@+id/textLike" android:id="@+id/textLike"
@ -36,7 +37,7 @@
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="10dp"
android:gravity="center" android:gravity="center"
android:text="@string/please_login" android:text="@string/emptyList"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
</RelativeLayout> </RelativeLayout>

View File

@ -289,4 +289,6 @@
<string name="no_search_result">No results.</string> <string name="no_search_result">No results.</string>
<string name="error_occurred">Error</string> <string name="error_occurred">Error</string>
<string name="copied">Copied</string> <string name="copied">Copied</string>
<string name="downloadsucceeded">Downloaded</string>
<string name="share_with_time">Share with start time</string>
</resources> </resources>

View File

@ -4,15 +4,6 @@
<PreferenceCategory app:title="@string/downloads"> <PreferenceCategory app:title="@string/downloads">
<ListPreference
app:defaultValue=".mp4"
app:entries="@array/videoFormats"
app:entryValues="@array/videoFormatsValues"
app:icon="@drawable/ic_videocam"
app:key="video_format"
app:summary="@string/video_format_summary"
app:title="@string/video_format" />
<ListPreference <ListPreference
android:defaultValue="downloads" android:defaultValue="downloads"
android:entries="@array/downloadLocation" android:entries="@array/downloadLocation"
@ -31,6 +22,16 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/share">
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_time"
app:key="share_with_time_code"
app:title="@string/share_with_time" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/advanced"> <PreferenceCategory app:title="@string/advanced">
<SwitchPreferenceCompat <SwitchPreferenceCompat