refactor: combine submitting and voting SponsorBlock segments into one dialog

This commit is contained in:
Bnyro 2024-04-11 15:33:32 +02:00
parent a83620206c
commit feea8ac795
5 changed files with 172 additions and 194 deletions

View File

@ -1,15 +1,17 @@
package com.github.libretube.ui.dialogs
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log
import androidx.core.os.bundleOf
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.BuildConfig
import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.DialogSubmitSegmentBinding
import com.github.libretube.extensions.TAG
@ -20,11 +22,16 @@ import java.lang.Exception
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
class SubmitSegmentDialog : DialogFragment() {
private var videoId: String = ""
private var currentPosition: Long = 0
private var duration: Long? = null
private var segments: List<Segment> = emptyList()
private var _binding: DialogSubmitSegmentBinding? = null
private val binding: DialogSubmitSegmentBinding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -36,37 +43,34 @@ class SubmitSegmentDialog : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogSubmitSegmentBinding.inflate(layoutInflater)
_binding = DialogSubmitSegmentBinding.inflate(layoutInflater)
binding.createSegment.setOnClickListener {
lifecycleScope.launch { createSegment() }
}
binding.voteSegment.setOnClickListener {
lifecycleScope.launch { voteForSegment() }
}
binding.startTime.setText((currentPosition.toFloat() / 1000).toString())
binding.segmentCategory.items = resources.getStringArray(R.array.sponsorBlockSegmentNames).toList()
return MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.sb_create_segment))
.setView(binding.root)
.setPositiveButton(R.string.okay, null)
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.vote_for_segment) { _, _ ->
VoteForSegmentDialog().apply {
arguments = bundleOf(IntentData.videoId to videoId)
}.show(parentFragmentManager, null)
}
.show()
.apply {
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
requireDialog().hide()
lifecycleScope.launch(Dispatchers.IO) {
fetchSegments()
}
lifecycleScope.launch {
submitSegment(binding)
dismiss()
}
}
}
return MaterialAlertDialogBuilder(requireContext())
.setView(binding.root)
.setNegativeButton(R.string.cancel, null)
.show()
}
private suspend fun submitSegment(binding: DialogSubmitSegmentBinding) {
private suspend fun createSegment() {
val context = requireContext().applicationContext
val binding = _binding ?: return
requireDialog().hide()
val startTime = binding.startTime.text.toString().toFloatOrNull()
var endTime = binding.endTime.text.toString().toFloatOrNull()
@ -96,5 +100,69 @@ class SubmitSegmentDialog : DialogFragment() {
Log.e(TAG(), e.toString())
context.toastFromMainDispatcher(e.localizedMessage.orEmpty())
}
requireDialog().dismiss()
}
private suspend fun voteForSegment() {
val binding = _binding ?: return
val context = requireContext().applicationContext
val segmentID = segments.getOrNull(binding.segmentsDropdown.selectedItemPosition)
?.uuid ?: return
// see https://wiki.sponsor.ajay.app/w/API_Docs#POST_/api/voteOnSponsorTime
val score = when {
binding.upvote.isChecked -> 1
binding.downvote.isChecked -> 0
else -> 20
}
dialog?.hide()
lifecycleScope.launch(Dispatchers.IO) {
try {
RetrofitInstance.externalApi.voteOnSponsorTime(
uuid = segmentID,
userID = PreferenceHelper.getSponsorBlockUserID(),
score = score
)
context.toastFromMainDispatcher(R.string.success)
} catch (e: Exception) {
context.toastFromMainDispatcher(e.localizedMessage.orEmpty())
}
withContext(Dispatchers.Main) { dialog?.dismiss() }
}
}
private suspend fun fetchSegments() {
val categories = resources.getStringArray(R.array.sponsorBlockSegments).toList()
segments = try {
RetrofitInstance.api.getSegments(videoId, JsonHelper.json.encodeToString(categories)).segments
} catch (e: Exception) {
Log.e(TAG(), e.toString())
return
}
withContext(Dispatchers.Main) {
val binding = _binding ?: return@withContext
if (segments.isEmpty()) {
dismiss()
Toast.makeText(context, R.string.no_segments_found, Toast.LENGTH_SHORT).show()
return@withContext
}
binding.segmentsDropdown.items = segments.map {
val (start, end) = it.segmentStartAndEnd
val (startStr, endStr) = DateUtils.formatElapsedTime(start.toLong()) to
DateUtils.formatElapsedTime(end.toLong())
"${it.category} ($startStr - $endStr)"
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,112 +0,0 @@
package com.github.libretube.ui.dialogs
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.DialogVoteSegmentBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
class VoteForSegmentDialog : DialogFragment() {
private lateinit var videoId: String
private var _binding: DialogVoteSegmentBinding? = null
private var segments: List<Segment> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
videoId = arguments?.getString(IntentData.videoId, "")!!
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
_binding = DialogVoteSegmentBinding.inflate(layoutInflater)
lifecycleScope.launch(Dispatchers.IO) {
fetchSegments()
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.vote_for_segment)
.setView(_binding?.root)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.okay, null)
.show()
.apply {
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val binding = _binding ?: return@setOnClickListener
val segmentID = segments.getOrNull(binding.segmentsDropdown.selectedItemPosition)
?.uuid ?: return@setOnClickListener
// see https://wiki.sponsor.ajay.app/w/API_Docs#POST_/api/voteOnSponsorTime
val score = when {
binding.upvote.isChecked -> 1
binding.downvote.isChecked -> 0
else -> 20
}
dialog?.hide()
lifecycleScope.launch(Dispatchers.IO) {
try {
RetrofitInstance.externalApi.voteOnSponsorTime(
uuid = segmentID,
userID = PreferenceHelper.getSponsorBlockUserID(),
score = score
)
context.toastFromMainDispatcher(R.string.success)
} catch (e: Exception) {
context.toastFromMainDispatcher(e.localizedMessage.orEmpty())
}
withContext(Dispatchers.Main) { dialog?.dismiss() }
}
}
}
}
private suspend fun fetchSegments() {
val categories = resources.getStringArray(R.array.sponsorBlockSegments).toList()
segments = try {
RetrofitInstance.api.getSegments(videoId, JsonHelper.json.encodeToString(categories)).segments
} catch (e: Exception) {
Log.e(TAG(), e.toString())
return
}
withContext(Dispatchers.Main) {
val binding = _binding ?: return@withContext
if (segments.isEmpty()) {
dismiss()
Toast.makeText(context, R.string.no_segments_found, Toast.LENGTH_SHORT).show()
return@withContext
}
binding.segmentsDropdown.items = segments.map {
val (start, end) = it.segmentStartAndEnd
val (startStr, endStr) = DateUtils.formatElapsedTime(start.toLong()) to
DateUtils.formatElapsedTime(end.toLong())
"${it.category} ($startStr - $endStr)"
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,21 +1,29 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
android:orientation="vertical"
android:paddingHorizontal="15dp"
android:paddingTop="15dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sb_create_segment"
android:textSize="24sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
style="@style/CustomDialogTextInputLayout"
android:hint="@string/start_time"
android:layout_width="0dp"
android:layout_weight="1">
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/start_time">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/start_time"
@ -25,10 +33,11 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/CustomDialogTextInputLayout"
android:hint="@string/end_time"
android:layout_width="0dp"
android:layout_weight="1">
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:hint="@string/end_time">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/end_time"
@ -43,8 +52,66 @@
android:id="@+id/segment_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
app:hint="@string/segment_type"
app:icon="@drawable/ic_frame" />
<com.google.android.material.button.MaterialButton
android:id="@+id/create_segment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:drawableStart="@drawable/ic_copy"
android:text="@string/sb_create_segment" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/vote_for_segment"
android:textSize="24sp" />
<com.github.libretube.ui.views.DropdownMenu
android:id="@+id/segments_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hint="@string/segment"
app:icon="@drawable/ic_frame" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal">
<RadioButton
android:id="@+id/upvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:checked="true"
android:text="@string/upvote" />
<RadioButton
android:id="@+id/downvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@string/downvote" />
<RadioButton
android:id="@+id/undo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/undo" />
</RadioGroup>
<com.google.android.material.button.MaterialButton
android:id="@+id/vote_segment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:drawableStart="@drawable/ic_copy"
android:text="@string/vote_for_segment" />
</LinearLayout>

View File

@ -1,45 +0,0 @@
<?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="12dp">
<com.github.libretube.ui.views.DropdownMenu
android:id="@+id/segments_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hint="@string/segment"
app:icon="@drawable/ic_frame" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal">
<RadioButton
android:id="@+id/upvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:checked="true"
android:text="@string/upvote" />
<RadioButton
android:id="@+id/downvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@string/downvote" />
<RadioButton
android:id="@+id/undo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/undo" />
</RadioGroup>
</LinearLayout>

View File

@ -47,8 +47,8 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">15dp</item>
<item name="android:layout_marginRight">15dp</item>
<item name="android:layout_marginStart">15dp</item>
<item name="android:layout_marginEnd">15dp</item>
<item name="android:layout_marginTop">5dp</item>
<item name="android:layout_marginBottom">10dp</item>