diff --git a/app/build.gradle b/app/build.gradle index 9067f4ccd..3380516fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,8 +97,6 @@ dependencies { // Do not update jackson annotations! It does not supports < API 26. implementation libs.jacksonAnnotations - implementation libs.mobileffmpeg - coreLibraryDesugaring libs.desugaring implementation libs.cronet.embedded implementation libs.cronet.okhttp diff --git a/app/src/main/java/com/github/libretube/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/activities/MainActivity.kt index aac8d554a..363944b56 100644 --- a/app/src/main/java/com/github/libretube/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/activities/MainActivity.kt @@ -193,8 +193,10 @@ class MainActivity : AppCompatActivity() { }) searchView.setOnCloseListener { - onBackPressed() - true + if (navController.currentDestination?.id == R.id.searchFragment) { + onBackPressed() + } + false } return super.onCreateOptionsMenu(menu) } diff --git a/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt b/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt index 3a76bbb68..793440518 100644 --- a/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt @@ -13,18 +13,15 @@ import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.dialogs.PlaylistOptionsDialog import com.github.libretube.dialogs.VideoOptionsDialog 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.NavigationHelper -import com.github.libretube.util.RetrofitInstance +import com.github.libretube.util.SubscriptionHelper import com.github.libretube.util.formatShort import com.github.libretube.util.setWatchProgressLength import com.github.libretube.util.toID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.io.IOException class SearchAdapter( private val searchItems: MutableList, @@ -128,80 +125,41 @@ class SearchAdapter( NavigationHelper.navigateChannel(root.context, item.url) } val channelId = item.url.toID() - val token = PreferenceHelper.getToken() - // only show subscribe button if logged in - if (token != "") isSubscribed(channelId, token, binding) + isSubscribed(channelId, binding) } } - private fun isSubscribed(channelId: String, token: String, binding: ChannelRowBinding) { - var isSubscribed = false - + private fun isSubscribed(channelId: String, binding: ChannelRowBinding) { // check whether the user subscribed to the channel CoroutineScope(Dispatchers.Main).launch { - val response = try { - RetrofitInstance.authApi.isSubscribed( - channelId, - token - ) - } catch (e: Exception) { - return@launch - } + var isSubscribed = SubscriptionHelper.isSubscribed(channelId) // if subscribed change text to unsubscribe - if (response.subscribed == true) { - isSubscribed = true + if (isSubscribed == true) { binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe) } // make sub button visible and set the on click listeners to (un)subscribe - if (response.subscribed != null) { - binding.searchSubButton.visibility = View.VISIBLE + if (isSubscribed == null) return@launch + binding.searchSubButton.visibility = View.VISIBLE - binding.searchSubButton.setOnClickListener { - if (!isSubscribed) { - subscribe(token, channelId) - binding.searchSubButton.text = - binding.root.context.getString(R.string.unsubscribe) - isSubscribed = true - } else { - unsubscribe(token, channelId) - binding.searchSubButton.text = - binding.root.context.getString(R.string.subscribe) - isSubscribed = false - } + binding.searchSubButton.setOnClickListener { + if (isSubscribed == false) { + SubscriptionHelper.subscribe(channelId) + binding.searchSubButton.text = + binding.root.context.getString(R.string.unsubscribe) + isSubscribed = true + } else { + SubscriptionHelper.unsubscribe(channelId) + binding.searchSubButton.text = + binding.root.context.getString(R.string.subscribe) + 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) { binding.apply { ConnectionHelper.loadImage(item.thumbnail, searchThumbnail) diff --git a/app/src/main/java/com/github/libretube/adapters/SubscriptionChannelAdapter.kt b/app/src/main/java/com/github/libretube/adapters/SubscriptionChannelAdapter.kt index 7e33c0c35..9d4af078b 100644 --- a/app/src/main/java/com/github/libretube/adapters/SubscriptionChannelAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/SubscriptionChannelAdapter.kt @@ -1,21 +1,15 @@ package com.github.libretube.adapters -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R import com.github.libretube.databinding.ChannelSubscriptionRowBinding -import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscription -import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.util.ConnectionHelper 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 kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class SubscriptionChannelAdapter(private val subscriptions: MutableList) : RecyclerView.Adapter() { @@ -46,51 +40,17 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList= 1) binding.audioSpinner.setSelection(1) binding.download.setOnClickListener { - val selectedAudioUrl = audioUrl[binding.audioSpinner.selectedItemPosition] - val selectedVideoUrl = vidUrl[binding.videoSpinner.selectedItemPosition] + val selectedAudioUrl = + 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) - intent.putExtra("videoId", videoId) + intent.putExtra("videoName", streams.title) intent.putExtra("videoUrl", selectedVideoUrl) intent.putExtra("audioUrl", selectedAudioUrl) - intent.putExtra("duration", duration) context?.startService(intent) dismiss() } diff --git a/app/src/main/java/com/github/libretube/dialogs/ShareDialog.kt b/app/src/main/java/com/github/libretube/dialogs/ShareDialog.kt index dd81f0e96..7509229ce 100644 --- a/app/src/main/java/com/github/libretube/dialogs/ShareDialog.kt +++ b/app/src/main/java/com/github/libretube/dialogs/ShareDialog.kt @@ -13,7 +13,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder class ShareDialog( private val id: String, - private val isPlaylist: Boolean + private val isPlaylist: Boolean, + private val position: Long = 0L ) : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @@ -39,7 +40,14 @@ class ShareDialog( else -> instanceUrl } 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() intent.apply { diff --git a/app/src/main/java/com/github/libretube/fragments/ChannelFragment.kt b/app/src/main/java/com/github/libretube/fragments/ChannelFragment.kt index c3d2a49a6..9861963ed 100644 --- a/app/src/main/java/com/github/libretube/fragments/ChannelFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/ChannelFragment.kt @@ -5,17 +5,15 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.adapters.ChannelAdapter 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.RetrofitInstance +import com.github.libretube.util.SubscriptionHelper import com.github.libretube.util.formatShort import com.github.libretube.util.toID import retrofit2.HttpException @@ -31,7 +29,7 @@ class ChannelFragment : Fragment() { var nextPage: String? = null private var channelAdapter: ChannelAdapter? = null private var isLoading = true - private var isSubscribed: Boolean = false + private var isSubscribed: Boolean? = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -61,13 +59,7 @@ class ChannelFragment : Fragment() { val refreshChannel = { binding.channelRefresh.isRefreshing = true fetchChannel() - if (PreferenceHelper.getToken() != "") { - isSubscribed() - } else { - binding.channelSubscribe.setOnClickListener { - Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() - } - } + isSubscribed() } refreshChannel() binding.channelRefresh.setOnRefreshListener { @@ -90,76 +82,28 @@ class ChannelFragment : Fragment() { } private fun isSubscribed() { - fun run() { - lifecycleScope.launchWhenCreated { - val response = try { - val token = PreferenceHelper.getToken() - RetrofitInstance.authApi.isSubscribed( - channelId!!, - token - ) - } catch (e: Exception) { - Log.e(TAG, e.toString()) - return@launchWhenCreated + lifecycleScope.launchWhenCreated { + isSubscribed = SubscriptionHelper.isSubscribed(channelId!!) + if (isSubscribed == null) return@launchWhenCreated + + runOnUiThread { + if (isSubscribed == true) { + binding.channelSubscribe.text = getString(R.string.unsubscribe) } - runOnUiThread { - if (response.subscribed == true) { + binding.channelSubscribe.setOnClickListener { + binding.channelSubscribe.text = if (isSubscribed == true) { + SubscriptionHelper.unsubscribe(channelId!!) + isSubscribed = false + getString(R.string.subscribe) + } else { + SubscriptionHelper.subscribe(channelId!!) isSubscribed = true - binding.channelSubscribe.text = 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) - } - } + 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() { diff --git a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt index c2de71680..76c1742bd 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -53,9 +53,7 @@ import com.github.libretube.obj.ChapterSegment import com.github.libretube.obj.Playlist import com.github.libretube.obj.Segment import com.github.libretube.obj.Segments -import com.github.libretube.obj.StreamItem import com.github.libretube.obj.Streams -import com.github.libretube.obj.Subscribe import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceKeys 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.PlayerHelper import com.github.libretube.util.RetrofitInstance +import com.github.libretube.util.SubscriptionHelper import com.github.libretube.util.formatShort import com.github.libretube.util.hideKeyboard import com.github.libretube.util.toID @@ -117,8 +116,9 @@ class PlayerFragment : Fragment() { private var videoId: String? = null private var playlistId: String? = null private var channelId: String? = null - private var isSubscribed: Boolean = false + private var isSubscribed: Boolean? = false private var isLive = false + private lateinit var streams: Streams /** * for the transition @@ -175,7 +175,6 @@ class PlayerFragment : Fragment() { /** * for autoplay */ - private var relatedStreams: List? = arrayListOf() private var nextStreamId: String? = null private var playlistStreamIds: MutableList = arrayListOf() private var playlistNextPage: String? = null @@ -187,13 +186,6 @@ class PlayerFragment : Fragment() { private lateinit var mediaSessionConnector: MediaSessionConnector 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?) { super.onCreate(savedInstanceState) arguments?.let { @@ -540,7 +532,7 @@ class PlayerFragment : Fragment() { // share button binding.relPlayerShare.setOnClickListener { - val shareDialog = ShareDialog(videoId!!, false) + val shareDialog = ShareDialog(videoId!!, false, exoPlayer.currentPosition) shareDialog.show(childFragmentManager, "ShareDialog") } @@ -739,7 +731,7 @@ class PlayerFragment : Fragment() { private fun playVideo() { fun run() { lifecycleScope.launchWhenCreated { - val response = try { + streams = try { RetrofitInstance.api.getStreams(videoId!!) } catch (e: IOException) { println(e) @@ -751,21 +743,13 @@ class PlayerFragment : Fragment() { Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() 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 { // set media sources for the player - setResolutionAndSubtitles(response) + setResolutionAndSubtitles(streams) prepareExoPlayerView() - initializePlayerView(response) - seekToWatchPosition() + initializePlayerView(streams) + if (!isLive) seekToWatchPosition() exoPlayer.prepare() exoPlayer.play() exoPlayerView.useController = true @@ -776,7 +760,7 @@ class PlayerFragment : Fragment() { // prepare for autoplay initAutoPlay() if (watchHistoryEnabled) { - PreferenceHelper.addToWatchHistory(videoId!!, response) + PreferenceHelper.addToWatchHistory(videoId!!, streams) } } } @@ -828,7 +812,10 @@ class PlayerFragment : Fragment() { val watchPositions = PreferenceHelper.getWatchPositions() var position: Long? = null 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 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 // 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 - 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 // by making sure that the next and the current video aren't the same saveWatchPosition() + // forces the comments to reload for the new video + commentsLoaded = false + binding.commentsRecView.adapter = null if (videoId != nextStreamId) { // save the id of the next stream as videoId and load the next video videoId = nextStreamId @@ -1064,9 +1054,9 @@ class PlayerFragment : Fragment() { intent.action = Intent.ACTION_VIEW intent.setDataAndType(uri, "video/*") - intent.putExtra(Intent.EXTRA_TITLE, title) - intent.putExtra("title", title) - intent.putExtra("artist", uploader) + intent.putExtra(Intent.EXTRA_TITLE, streams.title) + intent.putExtra("title", streams.title) + intent.putExtra("artist", streams.uploader) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) try { @@ -1278,7 +1268,7 @@ class PlayerFragment : Fragment() { // get the name of the currently played chapter private fun getCurrentChapterIndex(): Int { val currentPosition = exoPlayer.currentPosition - var chapterIndex: Int? = null + var chapterIndex = 0 chapters.forEachIndexed { index, chapter -> // check whether the chapter start is greater than the current player position @@ -1287,7 +1277,7 @@ class PlayerFragment : Fragment() { chapterIndex = index } } - return chapterIndex!! + return chapterIndex } private fun setMediaSource( @@ -1525,7 +1515,12 @@ class PlayerFragment : Fragment() { playerNotification = PlayerNotificationManager .Builder(c, PLAYER_NOTIFICATION_ID, BACKGROUND_CHANNEL_ID) .setMediaDescriptionAdapter( - DescriptionAdapter(title, uploader, thumbnailUrl, requireContext()) + DescriptionAdapter( + streams.title!!, + streams.uploader!!, + streams.thumbnailUrl!!, + requireContext() + ) ) .build() @@ -1564,38 +1559,22 @@ class PlayerFragment : Fragment() { private fun isSubscribed() { fun run() { lifecycleScope.launchWhenCreated { - val response = try { - RetrofitInstance.authApi.isSubscribed( - channelId!!, - 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 - } + isSubscribed = SubscriptionHelper.isSubscribed(channelId!!) + + if (isSubscribed == null) return@launchWhenCreated runOnUiThread { - if (response.subscribed == true) { - isSubscribed = true + if (isSubscribed == true) { binding.playerSubscribe.text = getString(R.string.unsubscribe) } - if (response.subscribed != null) { - binding.playerSubscribe.setOnClickListener { - if (isSubscribed) { - unsubscribe(channelId!!) - binding.playerSubscribe.text = getString(R.string.subscribe) - } else { - subscribe(channelId!!) - binding.playerSubscribe.text = getString(R.string.unsubscribe) - } + binding.playerSubscribe.setOnClickListener { + if (isSubscribed == true) { + SubscriptionHelper.unsubscribe(channelId!!) + binding.playerSubscribe.text = getString(R.string.subscribe) + } else { + SubscriptionHelper.subscribe(channelId!!) + 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() } - 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) { this ?: return if (!isAdded) return // Fragment not attached to an Activity diff --git a/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt b/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt index 573657c66..4d1400cbc 100644 --- a/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt @@ -19,6 +19,7 @@ import com.github.libretube.obj.StreamItem import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.util.RetrofitInstance +import com.github.libretube.util.SubscriptionHelper import com.github.libretube.util.toID import com.google.android.material.dialog.MaterialAlertDialogBuilder import retrofit2.HttpException @@ -53,63 +54,57 @@ class SubscriptionsFragment : Fragment() { super.onViewCreated(view, savedInstanceState) token = PreferenceHelper.getToken() - if (token != "") { - binding.loginOrRegister.visibility = View.GONE - binding.subRefresh.isEnabled = true + binding.subRefresh.isEnabled = true - binding.subProgress.visibility = View.VISIBLE + binding.subProgress.visibility = View.VISIBLE - val grid = PreferenceHelper.getString( - PreferenceKeys.GRID_COLUMNS, - resources.getInteger(R.integer.grid_items).toString() - ) - binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt()) + val grid = PreferenceHelper.getString( + PreferenceKeys.GRID_COLUMNS, + resources.getInteger(R.integer.grid_items).toString() + ) + binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt()) + fetchFeed() + + binding.subRefresh.setOnRefreshListener { + fetchChannels() 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() { @@ -130,7 +125,10 @@ class SubscriptionsFragment : Fragment() { fun run() { lifecycleScope.launchWhenCreated { feed = try { - RetrofitInstance.authApi.getFeed(token) + if (token != "") RetrofitInstance.authApi.getFeed(token) + else RetrofitInstance.authApi.getUnauthenticatedFeed( + SubscriptionHelper.getFormattedLocalSubscriptions() + ) } catch (e: IOException) { Log.e(TAG, e.toString()) Log.e(TAG, "IOException, you might not have internet connection") @@ -148,15 +146,7 @@ class SubscriptionsFragment : Fragment() { showFeed() } else { runOnUiThread { - with(binding.boogh) { - 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.emptyFeed.visibility = View.VISIBLE } } binding.subProgress.visibility = View.GONE @@ -185,7 +175,10 @@ class SubscriptionsFragment : Fragment() { fun run() { lifecycleScope.launchWhenCreated { val response = try { - RetrofitInstance.authApi.subscriptions(token) + if (token != "") RetrofitInstance.authApi.subscriptions(token) + else RetrofitInstance.authApi.unauthenticatedSubscriptions( + SubscriptionHelper.getFormattedLocalSubscriptions() + ) } catch (e: IOException) { Log.e(TAG, e.toString()) Log.e(TAG, "IOException, you might not have internet connection") diff --git a/app/src/main/java/com/github/libretube/preferences/PreferenceHelper.kt b/app/src/main/java/com/github/libretube/preferences/PreferenceHelper.kt index 00e4800f0..4403eba5a 100644 --- a/app/src/main/java/com/github/libretube/preferences/PreferenceHelper.kt +++ b/app/src/main/java/com/github/libretube/preferences/PreferenceHelper.kt @@ -235,6 +235,21 @@ object PreferenceHelper { return getString(PreferenceKeys.ERROR_LOG, "") } + fun getLocalSubscriptions(): List { + val json = settings.getString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, "") + return try { + val type = object : TypeReference>() {} + mapper.readValue(json, type) + } catch (e: Exception) { + listOf() + } + } + + fun setLocalSubscriptions(channels: List) { + val json = mapper.writeValueAsString(channels) + editor.putString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, json).commit() + } + private fun getDefaultSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } diff --git a/app/src/main/java/com/github/libretube/preferences/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/preferences/PreferenceKeys.kt index 820172302..18ad2c013 100644 --- a/app/src/main/java/com/github/libretube/preferences/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/preferences/PreferenceKeys.kt @@ -61,7 +61,6 @@ object PreferenceKeys { /** * Download */ - const val DOWNLOAD_VIDEO_FORMAT = "video_format" const val DOWNLOAD_LOCATION = "download_location" const val DOWNLOAD_FOLDER = "download_folder" @@ -81,9 +80,15 @@ object PreferenceKeys { const val CLEAR_SEARCH_HISTORY = "clear_search_history" const val CLEAR_WATCH_HISTORY = "clear_watch_history" const val CLEAR_WATCH_POSITIONS = "clear_watch_positions" + const val SHARE_WITH_TIME_CODE = "share_with_time_code" /** * Error logs */ const val ERROR_LOG = "error_log" + + /** + * Data + */ + const val LOCAL_SUBSCRIPTIONS = "local_subscriptions" } diff --git a/app/src/main/java/com/github/libretube/services/DownloadService.kt b/app/src/main/java/com/github/libretube/services/DownloadService.kt index 266225939..a0d7c5cf3 100644 --- a/app/src/main/java/com/github/libretube/services/DownloadService.kt +++ b/app/src/main/java/com/github/libretube/services/DownloadService.kt @@ -17,7 +17,6 @@ import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import com.arthenica.ffmpegkit.FFmpegKit import com.github.libretube.DOWNLOAD_CHANNEL_ID import com.github.libretube.DOWNLOAD_FAILURE_NOTIFICATION_ID import com.github.libretube.DOWNLOAD_PENDING_NOTIFICATION_ID @@ -35,11 +34,9 @@ class DownloadService : Service() { private lateinit var notification: NotificationCompat.Builder private var downloadId: Long = -1 - private lateinit var videoId: String + private lateinit var videoName: String private lateinit var videoUrl: String private lateinit var audioUrl: String - private lateinit var extension: String - private var duration: Int = 0 private var downloadType: Int = 3 private lateinit var audioDir: File @@ -52,13 +49,11 @@ class DownloadService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - videoId = intent?.getStringExtra("videoId")!! + videoName = intent?.getStringExtra("videoName")!! videoUrl = intent.getStringExtra("videoUrl")!! audioUrl = intent.getStringExtra("audioUrl")!! - duration = intent.getIntExtra("duration", 1) - extension = PreferenceHelper.getString(PreferenceKeys.DOWNLOAD_VIDEO_FORMAT, ".mp4")!! - downloadType = if (audioUrl != "" && videoUrl != "") DownloadType.MUX - else if (audioUrl != "") DownloadType.AUDIO + + downloadType = if (audioUrl != "") DownloadType.AUDIO else if (videoUrl != "") DownloadType.VIDEO else DownloadType.NONE if (downloadType != DownloadType.NONE) { @@ -115,18 +110,8 @@ class DownloadService : Service() { IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ) 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 -> { - videoDir = File(libretubeDir, "$videoId-video") + videoDir = File(libretubeDir, videoName) downloadId = downloadManagerRequest( getString(R.string.video), getString(R.string.downloading), @@ -135,7 +120,7 @@ class DownloadService : Service() { ) } DownloadType.AUDIO -> { - audioDir = File(libretubeDir, "$videoId-audio") + audioDir = File(libretubeDir, videoName) downloadId = downloadManagerRequest( getString(R.string.audio), getString(R.string.downloading), @@ -146,6 +131,7 @@ class DownloadService : Service() { } } catch (e: IllegalArgumentException) { Log.e(TAG, "download error $e") + downloadFailedNotification() } } @@ -166,11 +152,6 @@ class DownloadService : Service() { downloadSucceededNotification() onDestroy() } - } else { - try { - muxDownloadedMedia() - } catch (e: Exception) { - } } } } @@ -233,7 +214,7 @@ class DownloadService : Service() { val builder = NotificationCompat.Builder(this@DownloadService, DOWNLOAD_CHANNEL_ID) .setSmallIcon(R.drawable.ic_download) .setContentTitle(resources.getString(R.string.success)) - .setContentText(getString(R.string.fail)) + .setContentText(getString(R.string.downloadsucceeded)) .setPriority(NotificationCompat.PRIORITY_HIGH) with(NotificationManagerCompat.from(this@DownloadService)) { // 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() { try { unregisterReceiver(onDownloadComplete) diff --git a/app/src/main/java/com/github/libretube/util/PipedApi.kt b/app/src/main/java/com/github/libretube/util/PipedApi.kt index 394da668e..36fc4902b 100644 --- a/app/src/main/java/com/github/libretube/util/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/util/PipedApi.kt @@ -99,6 +99,9 @@ interface PipedApi { @GET("feed") suspend fun getFeed(@Query("authToken") token: String?): List + @GET("feed/unauthenticated") + suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List + @GET("subscribed") suspend fun isSubscribed( @Query("channelId") channelId: String, @@ -108,6 +111,9 @@ interface PipedApi { @GET("subscriptions") suspend fun subscriptions(@Header("Authorization") token: String): List + @GET("subscriptions/unauthenticated") + suspend fun unauthenticatedSubscriptions(@Query("channels") channels: String): List + @POST("subscribe") suspend fun subscribe( @Header("Authorization") token: String, diff --git a/app/src/main/java/com/github/libretube/util/SubscriptionHelper.kt b/app/src/main/java/com/github/libretube/util/SubscriptionHelper.kt new file mode 100644 index 000000000..19511413f --- /dev/null +++ b/app/src/main/java/com/github/libretube/util/SubscriptionHelper.kt @@ -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(",") + } +} diff --git a/app/src/main/res/layout/dialog_download.xml b/app/src/main/res/layout/dialog_download.xml index b77541111..471ef9f5d 100644 --- a/app/src/main/res/layout/dialog_download.xml +++ b/app/src/main/res/layout/dialog_download.xml @@ -4,7 +4,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - + + + + + + + + + android:layout_margin="8dp" + android:visibility="gone" />