Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace format parameter slider with chips #301

Merged
merged 5 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Add support for querying the name from the call log (Issue: #291, PR: #298, @chenxiaolong)
* This requires the optional `READ_CALL_LOGS` permission to be granted.
* Add support for formatting the phone number as digits only or with the country-specific style (Issue: #290, PR: #299, @chenxiaolong)
* Replace slider UI components with chips and the ability to set custom values (Issue: #295, PR: #301, @chenxiaolong)

Non-user-facing changes:

Expand Down
99 changes: 99 additions & 0 deletions app/src/main/java/com/chiller3/bcr/FileRetentionDialogFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.chiller3.bcr

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.InputType
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import com.chiller3.bcr.databinding.DialogTextInputBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder

class FileRetentionDialogFragment : DialogFragment() {
companion object {
val TAG: String = FileRetentionDialogFragment::class.java.simpleName

const val RESULT_SUCCESS = "success"
}

private lateinit var prefs: Preferences
private lateinit var binding: DialogTextInputBinding
private var retention: Retention? = null
private var success: Boolean = false

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
prefs = Preferences(context)
retention = Retention.fromPreferences(prefs)

binding = DialogTextInputBinding.inflate(layoutInflater)

binding.message.setText(R.string.file_retention_dialog_message)

binding.text.inputType = InputType.TYPE_CLASS_NUMBER
binding.text.addTextChangedListener {
retention = if (it!!.isEmpty()) {
NoRetention
} else {
try {
val days = it.toString().toUInt()
if (days == 0U) {
NoRetention
} else {
DaysRetention(days)
}
} catch (e: NumberFormatException) {
binding.textLayout.error = getString(R.string.file_retention_error_too_large)
null
}
}

refreshHelperText()
refreshOkButtonEnabledState()
}
if (savedInstanceState == null) {
when (val r = retention!!) {
is DaysRetention -> binding.text.setText(r.days.toString())
NoRetention -> binding.text.setText("")
}
}

refreshHelperText()

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.file_retention_dialog_title)
.setView(binding.root)
.setPositiveButton(R.string.dialog_action_ok) { _, _ ->
prefs.outputRetention = retention!!
success = true
}
.setNegativeButton(R.string.dialog_action_cancel, null)
.create()
.apply {
setCanceledOnTouchOutside(false)
}
}

override fun onStart() {
super.onStart()
refreshOkButtonEnabledState()
}

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)

setFragmentResult(tag!!, bundleOf(RESULT_SUCCESS to success))
}

private fun refreshHelperText() {
binding.textLayout.helperText = retention?.toFormattedString(requireContext())
}

private fun refreshOkButtonEnabledState() {
(dialog as AlertDialog?)?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
retention != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle
import android.text.Annotation
import android.text.InputType
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.SpannedString
Expand All @@ -20,7 +21,7 @@ import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import com.chiller3.bcr.databinding.DialogFilenameTemplateBinding
import com.chiller3.bcr.databinding.DialogTextInputBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder

class FilenameTemplateDialogFragment : DialogFragment() {
Expand All @@ -32,7 +33,7 @@ class FilenameTemplateDialogFragment : DialogFragment() {

private lateinit var prefs: Preferences
private lateinit var highlighter: TemplateSyntaxHighlighter
private lateinit var binding: DialogFilenameTemplateBinding
private lateinit var binding: DialogTextInputBinding
private var template: Template? = null
private var success: Boolean = false

Expand All @@ -42,11 +43,13 @@ class FilenameTemplateDialogFragment : DialogFragment() {
highlighter = TemplateSyntaxHighlighter(context)
template = prefs.filenameTemplate ?: Preferences.DEFAULT_FILENAME_TEMPLATE

binding = DialogFilenameTemplateBinding.inflate(layoutInflater)
binding = DialogTextInputBinding.inflate(layoutInflater)

binding.message.movementMethod = LinkMovementMethod.getInstance()
binding.message.text = buildMessage()

binding.text.inputType = InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
// Make this non-multiline text box look like one
binding.text.setHorizontallyScrolling(false)
binding.text.maxLines = Int.MAX_VALUE
Expand Down Expand Up @@ -95,7 +98,9 @@ class FilenameTemplateDialogFragment : DialogFragment() {

refreshOkButtonEnabledState()
}
binding.text.setText(template.toString())
if (savedInstanceState == null) {
binding.text.setText(template!!.toString())
}

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.filename_template_dialog_title)
Expand All @@ -107,6 +112,7 @@ class FilenameTemplateDialogFragment : DialogFragment() {
.setNegativeButton(R.string.dialog_action_cancel, null)
.setNeutralButton(R.string.filename_template_dialog_action_reset_to_default) { _, _ ->
prefs.filenameTemplate = null
success = true
}
.create()
.apply {
Expand Down
125 changes: 125 additions & 0 deletions app/src/main/java/com/chiller3/bcr/FormatParamDialogFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.chiller3.bcr

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.InputType
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import com.chiller3.bcr.databinding.DialogTextInputBinding
import com.chiller3.bcr.format.Format
import com.chiller3.bcr.format.RangedParamInfo
import com.chiller3.bcr.format.RangedParamType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.NumberFormatException

class FormatParamDialogFragment : DialogFragment() {
companion object {
val TAG: String = FormatParamDialogFragment::class.java.simpleName

const val RESULT_SUCCESS = "success"
}

private lateinit var prefs: Preferences
private lateinit var format: Format
private lateinit var binding: DialogTextInputBinding
private var value: UInt? = null
private var success: Boolean = false

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
prefs = Preferences(context)
format = Format.fromPreferences(prefs).first

val paramInfo = format.paramInfo
if (paramInfo !is RangedParamInfo) {
throw IllegalStateException("Selected format is not configurable")
}

val multiplier = when (paramInfo.type) {
RangedParamType.CompressionLevel -> 1U
RangedParamType.Bitrate -> 1000U
}

binding = DialogTextInputBinding.inflate(layoutInflater)

binding.message.text = getString(
R.string.format_param_dialog_message,
paramInfo.format(context, paramInfo.range.first),
paramInfo.format(context, paramInfo.range.last),
)

// Try to detect if the displayed format is a prefix or suffix since it's not the same in
// every language (eg. "Level 8" vs "8级")
val translated = when (paramInfo.type) {
RangedParamType.CompressionLevel ->
getString(R.string.format_param_compression_level, "\u0000")
RangedParamType.Bitrate ->
getString(R.string.format_param_bitrate, "\u0000")
}
val placeholder = translated.indexOf('\u0000')
val hasPrefix = placeholder > 0
val hasSuffix = placeholder < translated.length - 1
if (hasPrefix) {
binding.textLayout.prefixText = translated.substring(0, placeholder).trimEnd()
}
if (hasSuffix) {
binding.textLayout.suffixText = translated.substring(placeholder + 1).trimStart()
}
if (hasPrefix && hasSuffix) {
binding.text.textAlignment = View.TEXT_ALIGNMENT_CENTER
} else if (hasSuffix) {
binding.text.textAlignment = View.TEXT_ALIGNMENT_TEXT_END
}

binding.text.inputType = InputType.TYPE_CLASS_NUMBER
binding.text.addTextChangedListener {
value = null

if (it!!.isNotEmpty()) {
try {
val newValue = it.toString().toUInt().times(multiplier.toULong())
if (newValue in paramInfo.range) {
value = newValue.toUInt()
}
} catch (e: NumberFormatException) {
// Ignore
}
}

refreshOkButtonEnabledState()
}

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.format_param_dialog_title)
.setView(binding.root)
.setPositiveButton(R.string.dialog_action_ok) { _, _ ->
prefs.setFormatParam(format, value!!)
success = true
}
.setNegativeButton(R.string.dialog_action_cancel, null)
.create()
.apply {
setCanceledOnTouchOutside(false)
}
}

override fun onStart() {
super.onStart()
refreshOkButtonEnabledState()
}

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)

setFragmentResult(tag!!, bundleOf(RESULT_SUCCESS to success))
}

private fun refreshOkButtonEnabledState() {
(dialog as AlertDialog?)?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = value != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ import android.view.ViewGroup
import androidx.fragment.app.setFragmentResultListener
import com.chiller3.bcr.databinding.OutputDirectoryBottomSheetBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.slider.Slider

class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment(), Slider.OnChangeListener {
private var _binding: OutputDirectoryBottomSheetBinding? = null
private val binding
get() = _binding!!

class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment() {
private lateinit var binding: OutputDirectoryBottomSheetBinding
private lateinit var prefs: Preferences
private lateinit var highlighter: TemplateSyntaxHighlighter

Expand All @@ -29,7 +25,7 @@ class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment(), Slider.O
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = OutputDirectoryBottomSheetBinding.inflate(inflater, container, false)
binding = OutputDirectoryBottomSheetBinding.inflate(inflater, container, false)

val context = requireContext()

Expand All @@ -45,13 +41,10 @@ class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment(), Slider.O
parentFragmentManager.beginTransaction(), FilenameTemplateDialogFragment.TAG)
}

binding.retentionSlider.valueFrom = 0f
binding.retentionSlider.valueTo = (Retention.all.size - 1).toFloat()
binding.retentionSlider.stepSize = 1f
binding.retentionSlider.setLabelFormatter {
Retention.all[it.toInt()].toFormattedString(context)
binding.editRetention.setOnClickListener {
FileRetentionDialogFragment().show(
parentFragmentManager.beginTransaction(), FileRetentionDialogFragment.TAG)
}
binding.retentionSlider.addOnChangeListener(this)

binding.reset.setOnClickListener {
prefs.outputDir = null
Expand All @@ -66,6 +59,9 @@ class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment(), Slider.O
refreshFilenameTemplate()
refreshOutputRetention()
}
setFragmentResultListener(FileRetentionDialogFragment.TAG) { _, _ ->
refreshOutputRetention()
}

refreshFilenameTemplate()
refreshOutputDir()
Expand All @@ -88,21 +84,20 @@ class OutputDirectoryBottomSheetFragment : BottomSheetDialogFragment(), Slider.O
}

private fun refreshOutputRetention() {
val days = Retention.fromPreferences(prefs)
binding.retentionSlider.value = Retention.all.indexOf(days).toFloat()

// Disable retention options if the template makes it impossible for the feature to work
val template = prefs.filenameTemplate ?: Preferences.DEFAULT_FILENAME_TEMPLATE
val locations = template.findVariableRef(OutputFilenameGenerator.DATE_VAR)
binding.retentionSlider.isEnabled = locations != null &&
val retentionUsable = locations != null &&
locations.second != setOf(Template.VariableRefLocation.Arbitrary)
}

override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
when (slider) {
binding.retentionSlider -> {
prefs.outputRetention = Retention.all[value.toInt()]
}
binding.retention.isEnabled = retentionUsable
binding.editRetention.isEnabled = retentionUsable

if (retentionUsable) {
val retention = Retention.fromPreferences(prefs)
binding.retention.text = retention.toFormattedString(requireContext())
} else {
binding.retention.setText(R.string.retention_unusable)
}
}

Expand Down
Loading