mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-13 22:00:30 +05:30
feat: support for contributing to DeArrow
This commit is contained in:
parent
feea8ac795
commit
cd1eb0f7ff
@ -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<SubmitSegmentResponse>
|
||||
|
||||
@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)
|
||||
*/
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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}"
|
||||
}
|
||||
}
|
||||
|
18
app/src/main/res/drawable/ic_dearrow.xml
Normal file
18
app/src/main/res/drawable/ic_dearrow.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="36"
|
||||
android:viewportHeight="36">
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="m34.221,18.268c0,4.489 -2.217,8.289 -5.096,11.231 -2.879,2.941 -6.599,4.643 -10.994,4.643 -4.394,0 -8.799,-1.002 -11.678,-3.944 -2.879,-2.941 -4.674,-7.441 -4.674,-11.93 0,-4.489 2.32,-8.453 5.199,-11.395 2.879,-2.941 6.761,-5.099 11.154,-5.099 4.393,0 8.466,1.963 11.345,4.904 2.88,2.941 4.745,7.101 4.745,11.589z"
|
||||
android:strokeWidth="2.96196"
|
||||
android:strokeColor="#000000" />
|
||||
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m21.223,17.841c0,1.643 -1.378,3.027 -3.022,3.027 -1.643,0 -2.918,-1.383 -2.918,-3.027 0,-1.643 1.275,-2.982 2.918,-2.982 1.643,0 3.022,1.338 3.022,2.982z"
|
||||
android:strokeWidth="0.616691" />
|
||||
|
||||
</vector>
|
59
app/src/main/res/layout/dialog_submit_dearrow.xml
Normal file
59
app/src/main/res/layout/dialog_submit_dearrow.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingTop="15dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/contribute_to_dearrow"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/title_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:checked="true"
|
||||
android:text="@string/title" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/thumbnail_time_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/thumbnail_time" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.github.libretube.ui.views.DropdownMenu
|
||||
android:id="@+id/dearrow_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:hint="@string/title"
|
||||
app:icon="@drawable/ic_label" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/thumbnail_time_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/thumbnail_time">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/thumbnail_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberDecimal" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
@ -81,6 +81,15 @@
|
||||
app:track="@drawable/player_switch_track"
|
||||
app:trackTint="#88ffffff" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/dearrow_submit"
|
||||
style="@style/PlayerControlTop"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:src="@drawable/ic_dearrow"
|
||||
android:tooltipText="@string/contribute_to_dearrow"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sb_submit"
|
||||
style="@style/PlayerControlTop"
|
||||
|
@ -434,6 +434,9 @@
|
||||
<string name="fallback_piped_proxy_summary">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</string>
|
||||
<string name="dearrow">Enable DeArrow</string>
|
||||
<string name="dearrow_summary">Show more accurate and less sensationalist titles and thumbnails. Increases loading times</string>
|
||||
<string name="contribute_to_dearrow">Contribute to DeArrow</string>
|
||||
<string name="title">Title</string>
|
||||
<string name="thumbnail_time">Thumbnail time</string>
|
||||
<string name="visibility">Visibility</string>
|
||||
<string name="visibility_public">Public</string>
|
||||
<string name="visibility_unlisted">Unlisted</string>
|
||||
|
@ -35,6 +35,13 @@
|
||||
app:summary="@string/dearrow_summary"
|
||||
app:title="@string/dearrow" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:summaryOff="@string/disabled"
|
||||
android:summaryOn="@string/enabled"
|
||||
app:defaultValue="false"
|
||||
app:key="dearrow_contribute_key"
|
||||
app:title="@string/contribute_to_dearrow" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:defaultValue="true"
|
||||
app:key="sb_highlights"
|
||||
|
Loading…
Reference in New Issue
Block a user