diff --git a/app/src/main/java/com/github/libretube/api/ExternalApi.kt b/app/src/main/java/com/github/libretube/api/ExternalApi.kt index 1047ce5a0..ab63bebc0 100644 --- a/app/src/main/java/com/github/libretube/api/ExternalApi.kt +++ b/app/src/main/java/com/github/libretube/api/ExternalApi.kt @@ -1,8 +1,10 @@ package com.github.libretube.api +import com.github.libretube.api.obj.DeArrowBody import com.github.libretube.api.obj.PipedInstance import com.github.libretube.api.obj.SubmitSegmentResponse import com.github.libretube.obj.update.UpdateInfo +import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Query @@ -23,15 +25,18 @@ interface ExternalApi { @POST("$SB_API_URL/api/skipSegments") suspend fun submitSegment( @Query("videoID") videoId: String, + @Query("userID") userID: String, + @Query("userAgent") userAgent: String, @Query("startTime") startTime: Float, @Query("endTime") endTime: Float, @Query("category") category: String, - @Query("userAgent") userAgent: String, - @Query("userID") userID: String, @Query("duration") duration: Float? = null, @Query("description") description: String = "" ): List + @POST("$SB_API_URL/api/branding") + suspend fun submitDeArrow(@Body body: DeArrowBody) + /** * @param score: 0 for downvote, 1 for upvote, 20 for undoing previous vote (if existent) */ diff --git a/app/src/main/java/com/github/libretube/api/obj/DeArrowBody.kt b/app/src/main/java/com/github/libretube/api/obj/DeArrowBody.kt new file mode 100644 index 000000000..68e876f56 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/obj/DeArrowBody.kt @@ -0,0 +1,23 @@ +package com.github.libretube.api.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeArrowBody( + val videoID: String, + val userID: String, + val userAgent: String, + val title: DeArrowSubmitTitle?, + val thumbnail: DeArrowSubmitThumbnail?, + val downvote: Boolean = false +) + +@Serializable +data class DeArrowSubmitTitle( + val title: String +) + +@Serializable +data class DeArrowSubmitThumbnail( + val timestamp: Float +) \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index 861eb0b8d..bf14c7104 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -146,6 +146,7 @@ object PreferenceKeys { const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads" const val DISABLE_VIDEO_IMAGE_PROXY = "disable_video_image_proxy" const val CONTRIBUTE_TO_SB = "sb_contribute_key" + const val CONTRIBUTE_TO_DEARROW = "dearrow_contribute_key" /** * History diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt new file mode 100644 index 000000000..407251cd0 --- /dev/null +++ b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt @@ -0,0 +1,103 @@ +package com.github.libretube.ui.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope +import com.github.libretube.R +import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.obj.DeArrowBody +import com.github.libretube.api.obj.DeArrowSubmitThumbnail +import com.github.libretube.api.obj.DeArrowSubmitTitle +import com.github.libretube.constants.IntentData +import com.github.libretube.databinding.DialogSubmitDearrowBinding +import com.github.libretube.extensions.toastFromMainDispatcher +import com.github.libretube.helpers.PreferenceHelper +import com.github.libretube.util.TextUtils +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class SubmitDeArrowDialog: DialogFragment() { + private var videoId: String = "" + private var currentPosition: Long = 0 + + private var _binding: DialogSubmitDearrowBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + videoId = it.getString(IntentData.videoId)!! + currentPosition = it.getLong(IntentData.currentPosition) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + _binding = DialogSubmitDearrowBinding.inflate(layoutInflater) + + binding.dearrowTitle.typingEnabled = true + binding.thumbnailTime.setText(currentPosition.toString()) + + binding.titleCheckbox.setOnCheckedChangeListener { _, isChecked -> + binding.dearrowTitle.isEnabled = isChecked + } + binding.thumbnailTimeCheckbox.setOnCheckedChangeListener { _, isChecked -> + binding.thumbnailTimeInputLayout.isEnabled = isChecked + } + + lifecycleScope.launch { fetchDeArrowData() } + + return MaterialAlertDialogBuilder(requireContext()) + .setView(binding.root) + .setPositiveButton(R.string.okay, null) + .setNegativeButton(R.string.cancel, null) + .show() + .apply { + getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { + lifecycleScope.launch { submitDeArrow() } + } + } + } + + private suspend fun fetchDeArrowData() { + val data = try { + withContext(Dispatchers.IO) { + RetrofitInstance.api.getDeArrowContent(videoId) + }.getOrElse(videoId) { return } + } catch (e: Exception) { + return + } + + binding.dearrowTitle.items = data.titles.map { it.title } + } + + private suspend fun submitDeArrow() { + val context = requireContext().applicationContext + requireDialog().hide() + + val userID = PreferenceHelper.getSponsorBlockUserID() + val userAgent = TextUtils.getUserAgent(context) + val title = binding.dearrowTitle.text + .takeIf { it.isNotEmpty() && binding.titleCheckbox.isChecked } + ?.let { DeArrowSubmitTitle(it) } + val thumbnail = binding.thumbnailTime.text.toString().toFloatOrNull() + ?.takeIf { binding.thumbnailTimeCheckbox.isChecked } + ?.let { DeArrowSubmitThumbnail(it) } + val requestBody = DeArrowBody(videoId, userID, userAgent, title, thumbnail) + + try { + // https://wiki.sponsor.ajay.app/w/API_Docs/DeArrow + withContext(Dispatchers.IO) { + RetrofitInstance.externalApi.submitDeArrow(requestBody) + } + context.toastFromMainDispatcher(R.string.success) + } catch (e: Exception) { + context.toastFromMainDispatcher(e.localizedMessage.orEmpty()) + } + + dialog?.dismiss() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt index 47d6adccd..a7b642a5a 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt @@ -17,6 +17,7 @@ import com.github.libretube.databinding.DialogSubmitSegmentBinding import com.github.libretube.extensions.TAG import com.github.libretube.extensions.toastFromMainDispatcher import com.github.libretube.helpers.PreferenceHelper +import com.github.libretube.util.TextUtils import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.lang.Exception import kotlinx.coroutines.Dispatchers @@ -86,14 +87,14 @@ class SubmitSegmentDialog : DialogFragment() { val categories = resources.getStringArray(R.array.sponsorBlockSegments) val category = categories[binding.segmentCategory.selectedItemPosition] - val userAgent = "${context.packageName}/${BuildConfig.VERSION_NAME}" + val userAgent = TextUtils.getUserAgent(context) val uuid = PreferenceHelper.getSponsorBlockUserID() val duration = duration?.let { it.toFloat() / 1000 } try { withContext(Dispatchers.IO) { RetrofitInstance.externalApi - .submitSegment(videoId, startTime, endTime, category, userAgent, uuid, duration) + .submitSegment(videoId, uuid, userAgent, startTime, endTime, category, duration) } context.toastFromMainDispatcher(R.string.segment_submitted) } catch (e: Exception) { diff --git a/app/src/main/java/com/github/libretube/ui/views/DropdownMenu.kt b/app/src/main/java/com/github/libretube/ui/views/DropdownMenu.kt index 34d4ccead..f51ce7441 100644 --- a/app/src/main/java/com/github/libretube/ui/views/DropdownMenu.kt +++ b/app/src/main/java/com/github/libretube/ui/views/DropdownMenu.kt @@ -1,6 +1,7 @@ package com.github.libretube.ui.views import android.content.Context +import android.text.InputType import android.util.AttributeSet import android.view.LayoutInflater import android.widget.ArrayAdapter @@ -37,6 +38,22 @@ class DropdownMenu( get() = adapter.getPosition(binding.autoCompleteTextView.text.toString()) set(index) = binding.autoCompleteTextView.setText(adapter.getItem(index), false) + val text get() = binding.autoCompleteTextView.text.toString() + + override fun setEnabled(enabled: Boolean) { + binding.textInputLayout.isEnabled = enabled + } + + override fun isEnabled(): Boolean { + return binding.textInputLayout.isEnabled + } + + var typingEnabled: Boolean + set(enabled) { + binding.autoCompleteTextView.inputType = if (enabled) InputType.TYPE_CLASS_TEXT else InputType.TYPE_NULL + } + get() = binding.autoCompleteTextView.inputType != InputType.TYPE_NULL + init { context.obtainStyledAttributes(attributeSet, R.styleable.DropdownMenu, 0, 0).use { binding.textInputLayout.hint = it.getString(R.styleable.DropdownMenu_hint) diff --git a/app/src/main/java/com/github/libretube/ui/views/OnlinePlayerView.kt b/app/src/main/java/com/github/libretube/ui/views/OnlinePlayerView.kt index 68e498a3f..083b85ac2 100644 --- a/app/src/main/java/com/github/libretube/ui/views/OnlinePlayerView.kt +++ b/app/src/main/java/com/github/libretube/ui/views/OnlinePlayerView.kt @@ -1,6 +1,7 @@ package com.github.libretube.ui.views import android.content.Context +import android.os.Bundle import android.util.AttributeSet import android.view.Window import androidx.core.os.bundleOf @@ -19,6 +20,7 @@ import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.WindowHelper import com.github.libretube.obj.BottomSheetItem import com.github.libretube.ui.base.BaseActivity +import com.github.libretube.ui.dialogs.SubmitDeArrowDialog import com.github.libretube.ui.dialogs.SubmitSegmentDialog import com.github.libretube.ui.interfaces.OnlinePlayerOptions import com.github.libretube.ui.models.PlayerViewModel @@ -162,19 +164,29 @@ class OnlinePlayerView( binding.sbSubmit.isVisible = PreferenceHelper.getBoolean(PreferenceKeys.CONTRIBUTE_TO_SB, false) binding.sbSubmit.setOnClickListener { - val currentPosition = player?.currentPosition?.takeIf { it != C.TIME_UNSET } ?: 0 - val duration = player?.duration?.takeIf { it != C.TIME_UNSET } - val videoId = PlayingQueue.getCurrent()?.url?.toID() ?: return@setOnClickListener - - val bundle = bundleOf( - IntentData.currentPosition to currentPosition, - IntentData.duration to duration, - IntentData.videoId to videoId - ) - val newSubmitSegmentDialog = SubmitSegmentDialog() - newSubmitSegmentDialog.arguments = bundle - newSubmitSegmentDialog.show((context as BaseActivity).supportFragmentManager, null) + val submitSegmentDialog = SubmitSegmentDialog() + submitSegmentDialog.arguments = buildSbBundleArgs() ?: return@setOnClickListener + submitSegmentDialog.show((context as BaseActivity).supportFragmentManager, null) } + + binding.dearrowSubmit.isVisible = PreferenceHelper.getBoolean(PreferenceKeys.CONTRIBUTE_TO_DEARROW, false) + binding.dearrowSubmit.setOnClickListener { + val submitDialog = SubmitDeArrowDialog() + submitDialog.arguments = buildSbBundleArgs() ?: return@setOnClickListener + submitDialog.show((context as BaseActivity).supportFragmentManager, null) + } + } + + private fun buildSbBundleArgs(): Bundle? { + val currentPosition = player?.currentPosition?.takeIf { it != C.TIME_UNSET } ?: 0 + val duration = player?.duration?.takeIf { it != C.TIME_UNSET } + val videoId = PlayingQueue.getCurrent()?.url?.toID() ?: return null + + return bundleOf( + IntentData.currentPosition to currentPosition, + IntentData.duration to duration, + IntentData.videoId to videoId + ) } override fun getWindow(): Window = currentWindow ?: activity.window diff --git a/app/src/main/java/com/github/libretube/util/TextUtils.kt b/app/src/main/java/com/github/libretube/util/TextUtils.kt index b3a0690ea..db6370537 100644 --- a/app/src/main/java/com/github/libretube/util/TextUtils.kt +++ b/app/src/main/java/com/github/libretube/util/TextUtils.kt @@ -5,6 +5,7 @@ import android.icu.text.RelativeDateTimeFormatter import android.net.Uri import android.os.Build import android.text.format.DateUtils +import com.github.libretube.BuildConfig import com.github.libretube.R import java.time.Instant import java.time.LocalDateTime @@ -98,4 +99,8 @@ object TextUtils { if (text.length <= maxLength) return text return text.take(maxLength) + "…" } + + fun getUserAgent(context: Context): String { + return "${context.packageName}/${BuildConfig.VERSION_NAME}" + } } diff --git a/app/src/main/res/drawable/ic_dearrow.xml b/app/src/main/res/drawable/ic_dearrow.xml new file mode 100644 index 000000000..5c3a09950 --- /dev/null +++ b/app/src/main/res/drawable/ic_dearrow.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/layout/dialog_submit_dearrow.xml b/app/src/main/res/layout/dialog_submit_dearrow.xml new file mode 100644 index 000000000..dee2d86ec --- /dev/null +++ b/app/src/main/res/layout/dialog_submit_dearrow.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/exo_styled_player_control_view.xml b/app/src/main/res/layout/exo_styled_player_control_view.xml index 351d79063..e6256650e 100644 --- a/app/src/main/res/layout/exo_styled_player_control_view.xml +++ b/app/src/main/res/layout/exo_styled_player_control_view.xml @@ -81,6 +81,15 @@ app:track="@drawable/player_switch_track" app:trackTint="#88ffffff" /> + + Load videos via the proxy if connecting to YouTube directly doesn\'t work for the current video (increases the initial loading times). If disabled, YouTube music content likely won\'t play due to YT restrictions Enable DeArrow Show more accurate and less sensationalist titles and thumbnails. Increases loading times + Contribute to DeArrow + Title + Thumbnail time Visibility Public Unlisted diff --git a/app/src/main/res/xml/sponsorblock_settings.xml b/app/src/main/res/xml/sponsorblock_settings.xml index 21e503636..cf06876e1 100644 --- a/app/src/main/res/xml/sponsorblock_settings.xml +++ b/app/src/main/res/xml/sponsorblock_settings.xml @@ -35,6 +35,13 @@ app:summary="@string/dearrow_summary" app:title="@string/dearrow" /> + +