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 package com.github.libretube.ui.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log import android.util.Log
import androidx.core.os.bundleOf import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.BuildConfig import com.github.libretube.BuildConfig
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.DialogSubmitSegmentBinding import com.github.libretube.databinding.DialogSubmitSegmentBinding
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
@ -20,11 +22,16 @@ import java.lang.Exception
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
class SubmitSegmentDialog : DialogFragment() { class SubmitSegmentDialog : DialogFragment() {
private var videoId: String = "" private var videoId: String = ""
private var currentPosition: Long = 0 private var currentPosition: Long = 0
private var duration: Long? = null 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -36,37 +43,34 @@ class SubmitSegmentDialog : DialogFragment() {
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 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.startTime.setText((currentPosition.toFloat() / 1000).toString())
binding.segmentCategory.items = resources.getStringArray(R.array.sponsorBlockSegmentNames).toList() binding.segmentCategory.items = resources.getStringArray(R.array.sponsorBlockSegmentNames).toList()
return MaterialAlertDialogBuilder(requireContext()) lifecycleScope.launch(Dispatchers.IO) {
.setTitle(getString(R.string.sb_create_segment)) fetchSegments()
.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 { return MaterialAlertDialogBuilder(requireContext())
submitSegment(binding) .setView(binding.root)
dismiss() .setNegativeButton(R.string.cancel, null)
} .show()
}
}
} }
private suspend fun submitSegment(binding: DialogSubmitSegmentBinding) { private suspend fun createSegment() {
val context = requireContext().applicationContext val context = requireContext().applicationContext
val binding = _binding ?: return
requireDialog().hide()
val startTime = binding.startTime.text.toString().toFloatOrNull() val startTime = binding.startTime.text.toString().toFloatOrNull()
var endTime = binding.endTime.text.toString().toFloatOrNull() var endTime = binding.endTime.text.toString().toFloatOrNull()
@ -96,5 +100,69 @@ class SubmitSegmentDialog : DialogFragment() {
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())
context.toastFromMainDispatcher(e.localizedMessage.orEmpty()) 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"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:baselineAligned="false"
android:baselineAligned="false"> android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/CustomDialogTextInputLayout"
android:hint="@string/start_time"
android:layout_width="0dp" 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 <com.google.android.material.textfield.TextInputEditText
android:id="@+id/start_time" android:id="@+id/start_time"
@ -25,10 +33,11 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<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_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 <com.google.android.material.textfield.TextInputEditText
android:id="@+id/end_time" android:id="@+id/end_time"
@ -43,8 +52,66 @@
android:id="@+id/segment_category" android:id="@+id/segment_category"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
app:hint="@string/segment_type" app:hint="@string/segment_type"
app:icon="@drawable/ic_frame" /> 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> </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_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">15dp</item> <item name="android:layout_marginStart">15dp</item>
<item name="android:layout_marginRight">15dp</item> <item name="android:layout_marginEnd">15dp</item>
<item name="android:layout_marginTop">5dp</item> <item name="android:layout_marginTop">5dp</item>
<item name="android:layout_marginBottom">10dp</item> <item name="android:layout_marginBottom">10dp</item>