diff --git a/app/src/main/java/com/github/libretube/ChannelFragment.kt b/app/src/main/java/com/github/libretube/ChannelFragment.kt index 96d3d77e9..b745a9b70 100644 --- a/app/src/main/java/com/github/libretube/ChannelFragment.kt +++ b/app/src/main/java/com/github/libretube/ChannelFragment.kt @@ -4,8 +4,8 @@ package com.github.libretube import android.annotation.SuppressLint import android.app.Activity import android.content.Context -import android.opengl.Visibility import android.os.Bundle +import android.text.TextUtils.substring import android.util.Log import android.util.TypedValue import android.view.LayoutInflater @@ -208,9 +208,11 @@ class ChannelFragment : Fragment() { refreshLayout?.isRefreshing = false; runOnUiThread { view.findViewById(R.id.channel_scrollView).visibility = View.VISIBLE - view.findViewById(R.id.channel_name).text=response.name + view.findViewById(R.id.channel_name).text = if (response.name?.length!! > 19) response.name.toString().substring(0,16) + "..." else response.name + val channelVerified = view.findViewById(R.id.channel_verified) + if (response.verified) channelVerified.visibility = View.VISIBLE view.findViewById(R.id.channel_subs).text=resources.getString(R.string.subscribers, response.subscriberCount.formatShort()) - view.findViewById(R.id.channel_description).text=response.description + view.findViewById(R.id.channel_description).text=response.description?.trim() val bannerImage = view.findViewById(R.id.channel_banner) val channelImage = view.findViewById(R.id.channel_image) Picasso.get().load(response.bannerUrl).into(bannerImage) diff --git a/app/src/main/java/com/github/libretube/MainActivity.kt b/app/src/main/java/com/github/libretube/MainActivity.kt index 1b52e5660..3192af72b 100644 --- a/app/src/main/java/com/github/libretube/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/MainActivity.kt @@ -18,6 +18,7 @@ import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.Button import android.widget.LinearLayout +import android.widget.Toast import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.widget.Toolbar import androidx.constraintlayout.motion.widget.MotionLayout @@ -44,6 +45,13 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) RetrofitInstance.url = sharedPreferences.getString("instance", "https://pipedapi.kavin.rocks/")!! + SponsorBlockSettings.sponsorBlockEnabled = sharedPreferences.getBoolean("sponsorblock_enabled_key", false) + SponsorBlockSettings.introEnabled = sharedPreferences.getBoolean("intro_category_key", false) + SponsorBlockSettings.selfPromoEnabled = sharedPreferences.getBoolean("selfpromo_category_key", false) + SponsorBlockSettings.interactionEnabled = sharedPreferences.getBoolean("interaction_category_key", false) + SponsorBlockSettings.sponsorsEnabled = sharedPreferences.getBoolean("sponsors_category_key", false) + SponsorBlockSettings.outroEnabled = sharedPreferences.getBoolean("outro_category_key", false) + DynamicColors.applyToActivitiesIfAvailable(application) val languageName = sharedPreferences.getString("language", "sys") if (languageName != "") { @@ -86,6 +94,12 @@ class MainActivity : AppCompatActivity() { navController = findNavController(R.id.fragment) bottomNavigationView.setupWithNavController(navController) + when (sharedPreferences.getString("default_tab", "home")!!) { + "home" -> navController.navigate(R.id.home2) + "subscriptions" -> navController.navigate(R.id.subscriptions) + "library" -> navController.navigate(R.id.library) + } + bottomNavigationView.setOnItemSelectedListener { when (it.itemId) { R.id.home2 -> { diff --git a/app/src/main/java/com/github/libretube/PipedApi.kt b/app/src/main/java/com/github/libretube/PipedApi.kt index 3442f1d32..5a3c4888f 100644 --- a/app/src/main/java/com/github/libretube/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/PipedApi.kt @@ -13,6 +13,9 @@ interface PipedApi { @GET("comments/{videoId}") suspend fun getComments(@Path("videoId") videoId: String): CommentsPage + @GET("sponsors/{videoId}") + suspend fun getSegments(@Path("videoId") videoId: String, @Query("category") category: String): Segments + @GET("nextpage/comments/{videoId}") suspend fun getCommentsNextPage(@Path("videoId") videoId: String, @Query("nextpage") nextPage: String): CommentsPage diff --git a/app/src/main/java/com/github/libretube/PlayerFragment.kt b/app/src/main/java/com/github/libretube/PlayerFragment.kt index 459a9809c..b0b24bae0 100644 --- a/app/src/main/java/com/github/libretube/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/PlayerFragment.kt @@ -14,6 +14,7 @@ import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.Environment import android.text.Html +import android.text.TextUtils import android.util.Log import android.view.LayoutInflater import android.view.View @@ -35,6 +36,8 @@ import androidx.recyclerview.widget.RecyclerView import com.github.libretube.adapters.CommentsAdapter import com.github.libretube.adapters.TrendingAdapter import com.github.libretube.obj.PipedStream +import com.github.libretube.obj.Segment +import com.github.libretube.obj.Segments import com.github.libretube.obj.Subscribe import com.google.android.exoplayer2.C import com.google.android.exoplayer2.ExoPlayer @@ -87,6 +90,7 @@ class PlayerFragment : Fragment() { private lateinit var motionLayout: MotionLayout private lateinit var exoPlayer: ExoPlayer private lateinit var mediaSource: MediaSource + private lateinit var segmentData: Segments private lateinit var relDownloadVideo: LinearLayout @@ -270,6 +274,26 @@ class PlayerFragment : Fragment() { } } + private fun checkForSegments() + { + if (!exoPlayer.isPlaying || !SponsorBlockSettings.sponsorBlockEnabled) return + + exoPlayerView.postDelayed(this::checkForSegments, 100) + + if(segmentData.segments.isEmpty() ) + return + + segmentData.segments.forEach { segment: Segment -> + val segmentStart = (segment.segment!![0] * 1000.0f).toLong() + val segmentEnd = (segment.segment!![1] * 1000.0f).toLong() + val currentPosition = exoPlayer.currentPosition + if(currentPosition in segmentStart until segmentEnd) { + Toast.makeText(context,R.string.segment_skipped, Toast.LENGTH_SHORT).show() + exoPlayer.seekTo(segmentEnd); + } + } + } + private fun fetchJson(view: View) { fun run() { lifecycleScope.launchWhenCreated { @@ -297,6 +321,45 @@ class PlayerFragment : Fragment() { Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show() return@launchWhenCreated } + if(SponsorBlockSettings.sponsorBlockEnabled) { + val categories: ArrayList = arrayListOf() + if (SponsorBlockSettings.introEnabled) { + categories.add("intro") + } + if (SponsorBlockSettings.selfPromoEnabled) { + categories.add("selfpromo") + } + if (SponsorBlockSettings.interactionEnabled) { + categories.add("interaction") + } + if (SponsorBlockSettings.sponsorsEnabled) { + categories.add("sponsor") + } + if (SponsorBlockSettings.outroEnabled) { + categories.add("outro") + } + if(categories.size > 0) { + segmentData = try { + + RetrofitInstance.api.getSegments( + videoId!!, + "[\"" + TextUtils.join("\",\"", categories) + "\"]" + ) + } catch (e: IOException) { + println(e) + Log.e(TAG, "IOException, you might not have internet connection") + Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT) + .show() + return@launchWhenCreated + } catch (e: HttpException) { + Log.e(TAG, "HttpException, unexpected response") + Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT) + .show() + return@launchWhenCreated + } + } + } + isLoading = false var videosNameArray: Array = arrayOf() videosNameArray += "HLS" @@ -502,6 +565,13 @@ class PlayerFragment : Fragment() { } // Listener for play and pause icon change exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener { + override fun onIsPlayingChanged(isPlaying: Boolean) { + if(isPlaying && SponsorBlockSettings.sponsorBlockEnabled) + { + exoPlayerView.postDelayed(this@PlayerFragment::checkForSegments, 100) + } + } + override fun onPlayerStateChanged( playWhenReady: Boolean, playbackState: Int @@ -536,9 +606,9 @@ class PlayerFragment : Fragment() { view.findViewById(R.id.player_description).text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - Html.fromHtml(response.description, Html.FROM_HTML_MODE_COMPACT) + Html.fromHtml(response.description, Html.FROM_HTML_MODE_COMPACT).trim() } else { - Html.fromHtml(response.description) + Html.fromHtml(response.description).trim() } view.findViewById(R.id.player_views_info).text = response.views.formatShort() + " views • " + response.uploadDate diff --git a/app/src/main/java/com/github/libretube/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/PlaylistFragment.kt index 89943781a..ef6ab7f6d 100644 --- a/app/src/main/java/com/github/libretube/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/PlaylistFragment.kt @@ -73,7 +73,7 @@ class PlaylistFragment : Fragment() { val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE) val user = sharedPref2?.getString("username","") var isOwner = false - if(response.uploaderUrl == null && response.uploader == user){ + if(response.uploaderUrl == null && response.uploader.equals(user, true)){ isOwner = true } playlistAdapter = PlaylistAdapter(response.relatedStreams!!.toMutableList(), playlist_id!!, isOwner, requireActivity()) diff --git a/app/src/main/java/com/github/libretube/SettingsActivity.kt b/app/src/main/java/com/github/libretube/SettingsActivity.kt index 7e1347b6f..91fe08dfe 100644 --- a/app/src/main/java/com/github/libretube/SettingsActivity.kt +++ b/app/src/main/java/com/github/libretube/SettingsActivity.kt @@ -39,7 +39,9 @@ class SettingsActivity : AppCompatActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - overridePendingTransition(50, 50); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + overridePendingTransition(50, 50); + } val view = this.findViewById(android.R.id.content) view.setAlpha(0F); view.animate().alpha(1F).setDuration(300); @@ -158,6 +160,15 @@ class SettingsActivity : AppCompatActivity(), true } + val sponsorblock = findPreference("sponsorblock") + sponsorblock?.setOnPreferenceClickListener { + val newFragment = SponsorBlockSettings() + parentFragmentManager.beginTransaction() + .replace(R.id.settings, newFragment) + .commitNow() + true + } + val importFromYt = findPreference("import_from_yt") importFromYt?.setOnPreferenceClickListener { val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) diff --git a/app/src/main/java/com/github/libretube/SponsorBlockSettings.kt b/app/src/main/java/com/github/libretube/SponsorBlockSettings.kt new file mode 100644 index 000000000..813a3f472 --- /dev/null +++ b/app/src/main/java/com/github/libretube/SponsorBlockSettings.kt @@ -0,0 +1,55 @@ +package com.github.libretube + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat + +class SponsorBlockSettings : PreferenceFragmentCompat() { + private val TAG = "SponsorBlockDialog" + companion object { + var sponsorBlockEnabled: Boolean = false + var sponsorsEnabled: Boolean = false + var selfPromoEnabled: Boolean = false + var interactionEnabled: Boolean = false + var introEnabled: Boolean = false + var outroEnabled: Boolean = false + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.sponsorblock_settings, rootKey) + val sponsorBlockToggle = findPreference("sponsorblock_enabled_key") + sponsorBlockToggle?.setOnPreferenceChangeListener { _, newValue -> + sponsorBlockEnabled = newValue as Boolean + true + } + + val sponsorToggle = findPreference("sponsors_category_key") + sponsorToggle?.setOnPreferenceChangeListener { _, newValue -> + sponsorsEnabled = newValue as Boolean + true + } + val selfPromoToggle = findPreference("selfpromo_category_key") + selfPromoToggle?.setOnPreferenceChangeListener { _, newValue -> + selfPromoEnabled = newValue as Boolean + true + } + + val interactionToggle = findPreference("interaction_category_key") + interactionToggle?.setOnPreferenceChangeListener { _, newValue -> + interactionEnabled = newValue as Boolean + true + } + + val introToggle = findPreference("intro_category_key") + introToggle?.setOnPreferenceChangeListener { _, newValue -> + introEnabled = newValue as Boolean + true + } + + val outroToggle = findPreference("outro_category_key") + outroToggle?.setOnPreferenceChangeListener { _, newValue -> + outroEnabled = newValue as Boolean + true + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/Subscriptions.kt b/app/src/main/java/com/github/libretube/Subscriptions.kt index 3fbfb8387..5db11caac 100644 --- a/app/src/main/java/com/github/libretube/Subscriptions.kt +++ b/app/src/main/java/com/github/libretube/Subscriptions.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.* +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager @@ -16,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.github.libretube.adapters.SubscriptionAdapter import com.github.libretube.adapters.SubscriptionChannelAdapter +import org.chromium.base.ThreadUtils.runOnUiThread import retrofit2.HttpException import java.io.IOException @@ -52,8 +54,6 @@ class Subscriptions : Fragment() { progressBar.visibility=View.VISIBLE var channelRecView = view.findViewById(R.id.sub_channels) - channelRecView?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - fetchChannels(channelRecView) var feedRecView = view.findViewById(R.id.sub_feed) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) @@ -66,6 +66,25 @@ class Subscriptions : Fragment() { fetchFeed(feedRecView, progressBar, view) } + var toggleSubs = view.findViewById(R.id.toggle_subs) + toggleSubs.visibility = View.VISIBLE + var loadedSubbedChannels = false + toggleSubs.setOnClickListener { + if (!channelRecView.isVisible) { + if (!loadedSubbedChannels) { + channelRecView?.layoutManager = GridLayoutManager(context, 4) + fetchChannels(channelRecView) + loadedSubbedChannels = true + } + channelRecView.visibility = View.VISIBLE + feedRecView.visibility = View.GONE + } + else { + channelRecView.visibility = View.GONE + feedRecView.visibility = View.VISIBLE + } + } + val scrollView = view.findViewById(R.id.scrollview_sub) scrollView.viewTreeObserver .addOnScrollChangedListener { diff --git a/app/src/main/java/com/github/libretube/adapters/CommentsAdapter.kt b/app/src/main/java/com/github/libretube/adapters/CommentsAdapter.kt index bdd0f0811..36bdcaf58 100644 --- a/app/src/main/java/com/github/libretube/adapters/CommentsAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/CommentsAdapter.kt @@ -1,11 +1,15 @@ package com.github.libretube.adapters +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.os.bundleOf import androidx.recyclerview.widget.RecyclerView +import com.github.libretube.MainActivity import com.github.libretube.R import com.github.libretube.formatShort import com.github.libretube.obj.Comment @@ -27,8 +31,8 @@ class CommentsAdapter(private val comments: MutableList): RecyclerView override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.v.findViewById(R.id.comment_infos).text = comments[position].author.toString() + " • " + comments[position].commentedTime.toString() holder.v.findViewById(R.id.comment_text).text = comments[position].commentText.toString() - val thumbnailImage = holder.v.findViewById(R.id.commentor_image) - Picasso.get().load(comments[position].thumbnail).fit().centerCrop().into(thumbnailImage) + val channelImage = holder.v.findViewById(R.id.commentor_image) + Picasso.get().load(comments[position].thumbnail).fit().centerCrop().into(channelImage) holder.v.findViewById(R.id.likes_textView).text = comments[position].likeCount?.toLong().formatShort() if (comments[position].verified == true) { holder.v.findViewById(R.id.verified_imageView).visibility = View.VISIBLE @@ -39,6 +43,20 @@ class CommentsAdapter(private val comments: MutableList): RecyclerView if (comments[position].hearted == true) { holder.v.findViewById(R.id.hearted_imageView).visibility = View.VISIBLE } + channelImage.setOnClickListener{ + val activity = holder.v.context as MainActivity + val bundle = bundleOf("channel_id" to comments[position].commentorUrl) + activity.navController.navigate(R.id.channel, bundle) + try { + val mainMotionLayout = activity.findViewById(R.id.mainMotionLayout) + if (mainMotionLayout.progress == 0.toFloat()) { + mainMotionLayout.transitionToEnd() + activity.findViewById(R.id.playerMotionLayout).transitionToEnd() + } + }catch (e: Exception){ + + } + } } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/github/libretube/obj/Segment.kt b/app/src/main/java/com/github/libretube/obj/Segment.kt new file mode 100644 index 000000000..4608971b7 --- /dev/null +++ b/app/src/main/java/com/github/libretube/obj/Segment.kt @@ -0,0 +1,12 @@ +package com.github.libretube.obj + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +data class Segment( + val actionType: String?, + val category: String?, + val segment: List? +){ + constructor(): this("", "", arrayListOf()) +} diff --git a/app/src/main/java/com/github/libretube/obj/Segments.kt b/app/src/main/java/com/github/libretube/obj/Segments.kt new file mode 100644 index 000000000..a6ed285bc --- /dev/null +++ b/app/src/main/java/com/github/libretube/obj/Segments.kt @@ -0,0 +1,10 @@ +package com.github.libretube.obj + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +data class Segments( + val segments: MutableList = arrayListOf() +){ + constructor(): this(arrayListOf()) +} diff --git a/app/src/main/res/drawable/ic_sponsorblock.xml b/app/src/main/res/drawable/ic_sponsorblock.xml new file mode 100644 index 000000000..ffdc099ab --- /dev/null +++ b/app/src/main/res/drawable/ic_sponsorblock.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/channel_subscription_row.xml b/app/src/main/res/layout/channel_subscription_row.xml index c9d1df987..97cbf0228 100644 --- a/app/src/main/res/layout/channel_subscription_row.xml +++ b/app/src/main/res/layout/channel_subscription_row.xml @@ -9,8 +9,8 @@ > + + + + + + - - + + + + + + + + L D + + + @string/startpage + @string/subscriptions + @string/library + + + + home + subscriptions + library + + HLS 1080p diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec277ead3..104aada90 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,4 +84,21 @@ Music Videos Music Albums Music Playlists + Default Tab + SponsorBlock + Uses API from https://sponsor.ajay.app/ + Skipped segment. + SponsorBlock + Enabled + Segments + Sponsor + Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like. + Unpaid/Self Promotion + Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with. + Interaction Reminder (Subscribe) + When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead. + Intermission/Intro Animation + An interval without actual content. Could be a pause, static frame, repeating animation. This should not be used for transitions containing information. + Endcards/Credits + Credits or when the YouTube endcards appear. Not for conclusions with information. diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 6a2755c1c..d28f3a464 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -71,6 +71,22 @@ android:icon="@drawable/ic_theme" /> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file