Skip to content

Commit

Permalink
Improved accessibility of compound amount inputs (original and equiva…
Browse files Browse the repository at this point in the history
…lent amount)
  • Loading branch information
mtotschnig committed Oct 21, 2024
1 parent c46fbf0 commit ec9d9e4
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,10 @@ import org.totschnig.myexpenses.util.formatMoney
import org.totschnig.myexpenses.util.getSortDirectionFromMenuItemId
import org.totschnig.myexpenses.util.safeMessage
import org.totschnig.myexpenses.util.setEnabledAndVisible
import org.totschnig.myexpenses.util.ui.DisplayProgress
import org.totschnig.myexpenses.util.ui.asDateTimeFormatter
import org.totschnig.myexpenses.util.ui.calcProgressVisualRepresentation
import org.totschnig.myexpenses.util.ui.dateTimeFormatter
import org.totschnig.myexpenses.util.ui.dateTimeFormatterLegacy
import org.totschnig.myexpenses.util.ui.forViewSystem
import org.totschnig.myexpenses.util.ui.getAmountColor
import org.totschnig.myexpenses.viewmodel.AccountSealedException
import org.totschnig.myexpenses.viewmodel.CompletedAction
Expand Down Expand Up @@ -2097,7 +2096,7 @@ abstract class BaseMyExpenses : LaunchActivity(), OnDialogResultListener, Contri
with(binding.toolbar.donutView) {
animateChanges = animateProgress
submitData(
sections = calcProgressVisualRepresentation(it).forViewSystem(
sections = DisplayProgress.calcProgressVisualRepresentation(it).forViewSystem(
account._color,
getAmountColor(account.criterion?.sign ?: 0)
).also {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
Expand All @@ -23,9 +22,6 @@ import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand All @@ -47,8 +43,7 @@ import app.futured.donut.compose.data.DonutModel
import org.totschnig.myexpenses.R
import org.totschnig.myexpenses.db2.FLAG_EXPENSE
import org.totschnig.myexpenses.db2.FLAG_INCOME
import org.totschnig.myexpenses.util.ui.calcProgressVisualRepresentation
import org.totschnig.myexpenses.util.ui.forCompose
import org.totschnig.myexpenses.util.ui.DisplayProgress
import kotlin.experimental.and
import kotlin.experimental.inv
import kotlin.experimental.or
Expand Down Expand Up @@ -119,7 +114,7 @@ fun DonutInABox(
gapAngleDegrees = 0f,
strokeWidth = LocalContext.current.resources.getDimension(R.dimen.progress_donut_stroke_width),
strokeCap = StrokeCap.Butt,
sections = calcProgressVisualRepresentation(progress).forCompose(color, excessColor)
sections = DisplayProgress.calcProgressVisualRepresentation(progress).forCompose(color, excessColor)
)
)
Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,10 @@ abstract class TransactionDelegate<T : ITransaction>(
) {
val text = appendCurrencySymbol(label.context, textResId, currencyUnit)
label.text = text
amountInput.contentDescription =
appendCurrencyDescription(label.context, textResId, currencyUnit)
if (amountInput.contentDescription.isNullOrEmpty()) {
amountInput.contentDescription =
appendCurrencyDescription(label.context, textResId, currencyUnit)
}
}

fun setCurrencies(currencies: List<Currency>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ import org.totschnig.myexpenses.model.Money
import org.totschnig.myexpenses.provider.DatabaseConstants.KEY_AMOUNT
import org.totschnig.myexpenses.util.formatMoney
import org.totschnig.myexpenses.util.getLocale
import org.totschnig.myexpenses.util.ui.calcProgressVisualRepresentation
import org.totschnig.myexpenses.util.ui.forCompose
import org.totschnig.myexpenses.util.ui.DisplayProgress
import org.totschnig.myexpenses.viewmodel.CriterionViewModel
import timber.log.Timber
import java.math.BigDecimal
Expand Down Expand Up @@ -289,7 +288,7 @@ fun GraphInternal(
modifier = modifier,
model = DonutModel(
cap = 100f,
sections = calcProgressVisualRepresentation(progress).forCompose(
sections = DisplayProgress.calcProgressVisualRepresentation(progress).forCompose(
progressColor,
overageColor
).also { Timber.d("sections: $it") },
Expand Down
38 changes: 27 additions & 11 deletions myExpenses/src/main/java/org/totschnig/myexpenses/ui/AmountInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.viewbinding.ViewBinding
import org.totschnig.myexpenses.R
import org.totschnig.myexpenses.adapter.CurrencyAdapter
Expand All @@ -25,6 +29,7 @@ import org.totschnig.myexpenses.model.CurrencyContext
import org.totschnig.myexpenses.model.CurrencyUnit
import org.totschnig.myexpenses.model.Money
import org.totschnig.myexpenses.util.ui.getActivity
import org.totschnig.myexpenses.util.ui.setHintForA11yOnly
import org.totschnig.myexpenses.viewmodel.data.Currency
import org.totschnig.myexpenses.viewmodel.data.Currency.Companion.create
import timber.log.Timber
Expand Down Expand Up @@ -66,6 +71,13 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
private var upStreamDependencyRef = -1
private var blockWatcher = false

/**
* the user of this component will usually set an extensive content description explaining how to
* use this component. In addition we need a short label (purpose) that will be used in the
* content description of child elements
*/
val purpose: CharSequence?

init {
val ta = context.obtainStyledAttributes(attrs, R.styleable.AmountInput)
withCurrencySelection = ta.getBoolean(R.styleable.AmountInput_withCurrencySelection, false)
Expand All @@ -80,6 +92,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
(if (viewBinding is AmountInputAlternateBinding) (viewBinding as AmountInputAlternateBinding).AmountCurrency else (viewBinding as AmountInputBinding).AmountCurrency)
.getRoot()
)
purpose = ta.getText(R.styleable.AmountInput_purpose)
updateChildContentDescriptions()
setWithTypeSwitch(ta.getBoolean(R.styleable.AmountInput_withTypeSwitch, true))
if (withCurrencySelection) {
Expand All @@ -88,7 +101,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup
parent: ViewGroup,
): View {
val view = super.getView(position, convertView, parent)
view.setPadding(
Expand All @@ -108,7 +121,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
parent: AdapterView<*>?,
view: View,
position: Int,
id: Long
id: Long,
) {
val currency = (currencySpinner.selectedItem as Currency?)!!.code
val currencyUnit: CurrencyUnit = host.currencyContext.get(currency)
Expand Down Expand Up @@ -183,9 +196,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
}

private fun updateChildContentDescriptions() {
//Edit Text does not use content description once it holds content. It is hence needed to point a textView
//in the neighborhood of this AmountInput directly to amountEdiText with android:labelFor="@id/AmountEditText"
//setContentDescriptionForChild(amountEditText, null);
setContentDescriptionForChild(amountEditText(), context.getString(R.string.amount))
setContentDescriptionForChild(
calculator(),
context.getString(R.string.content_description_calculator)
Expand All @@ -198,13 +209,18 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
}

private fun setContentDescriptionForChild(view: View, contentDescription: CharSequence?) {
view.setContentDescription(
if (contentDescription == null) getContentDescription() else String.format(
val parentContentDescription = purpose ?: this.contentDescription
val childContentDescription =
if (contentDescription == null) parentContentDescription else String.format(
"%s : %s",
getContentDescription(),
parentContentDescription,
contentDescription
)
)
if (view is EditText) {
view.setHintForA11yOnly(childContentDescription)
} else {
view.contentDescription = childContentDescription
}
}

private fun setContentDescriptionForTypeSwitch() {
Expand Down Expand Up @@ -312,7 +328,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con

fun getAmount(
currencyUnit: CurrencyUnit,
showToUser: Boolean = true
showToUser: Boolean = true,
): Result<Money?> = getTypedValue(showToUser).mapCatching { value ->
try {
(value ?: BigDecimal.ZERO.takeIf { !showToUser })?.let { Money(currencyUnit, it) }
Expand Down Expand Up @@ -490,7 +506,7 @@ class AmountInput(context: Context, attrs: AttributeSet?) : ConstraintLayout(con
amountEditTextState: Parcelable,
currencySpinnerState: Parcelable,
exchangeRateState: BigDecimal?,
focusedId: Int
focusedId: Int,
) : super(superState) {
this.typeButtonState = typeButtonState
this.amountEditTextState = amountEditTextState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.totschnig.myexpenses.databinding.ExchangeRateBinding
import org.totschnig.myexpenses.databinding.ExchangeRatesBinding
import org.totschnig.myexpenses.model.CurrencyUnit
import org.totschnig.myexpenses.util.crashreporting.CrashHandler.Companion.report
import org.totschnig.myexpenses.util.ui.setHintForA11yOnly
import org.totschnig.myexpenses.viewmodel.ExchangeRateViewModel
import java.math.BigDecimal
import java.math.MathContext
Expand All @@ -30,9 +31,9 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou
val rate2Edit: AmountEditText
private var exchangeRateWatcher: ExchangeRateWatcher? = null
private var blockWatcher = false
private var viewModel: ExchangeRateViewModel? = null
private var firstCurrency: CurrencyUnit? = null
private var secondCurrency: CurrencyUnit? = null
private lateinit var viewModel: ExchangeRateViewModel
private lateinit var firstCurrency: CurrencyUnit
private lateinit var secondCurrency: CurrencyUnit
private val binding = ExchangeRatesBinding.inflate(LayoutInflater.from(getContext()), this)
fun setExchangeRateWatcher(exchangeRateWatcher: ExchangeRateWatcher?) {
this.exchangeRateWatcher = exchangeRateWatcher
Expand All @@ -45,7 +46,7 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
viewModel!!.clear()
viewModel.clear()
}

private fun findLifecycleOwner(context: Context?): LifecycleOwner? {
Expand All @@ -62,14 +63,14 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou
viewModel = ExchangeRateViewModel((context.applicationContext as MyApplication))
val lifecycleOwner = findLifecycleOwner(context)
if (lifecycleOwner != null) {
viewModel!!.getData().observe(lifecycleOwner) { result: Double? ->
viewModel.getData().observe(lifecycleOwner) { result: Double? ->
rate2Edit.setAmount(
BigDecimal.valueOf(
result!!
)
)
}
viewModel!!.getError().observe(lifecycleOwner) { message: String -> complain(message) }
viewModel.getError().observe(lifecycleOwner) { message: String -> complain(message) }
} else {
report(Exception("No LifecycleOwner found"))
}
Expand All @@ -81,16 +82,14 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou

init {
binding.ivDownload.getRoot().setOnClickListener {
if (firstCurrency != null && secondCurrency != null && viewModel != null) {
viewModel!!.loadExchangeRate(firstCurrency!!.code, secondCurrency!!.code, host.date)
if (::firstCurrency.isInitialized && ::secondCurrency.isInitialized && ::viewModel.isInitialized) {
viewModel.loadExchangeRate(firstCurrency.code, secondCurrency.code, host.date)
}
}
rate1Edit = binding.ExchangeRate1.ExchangeRateText
rate1Edit.setId(R.id.ExchangeRateEdit1)
binding.ExchangeRate1.ExchangeRateLabel1.setLabelFor(R.id.ExchangeRateEdit1)
rate1Edit.id = R.id.ExchangeRateEdit1
rate2Edit = binding.ExchangeRate2.ExchangeRateText
rate2Edit.setId(R.id.ExchangeRateEdit2)
binding.ExchangeRate2.ExchangeRateLabel1.setLabelFor(R.id.ExchangeRateEdit2)
rate2Edit.id = R.id.ExchangeRateEdit2
rate1Edit.fractionDigits = EXCHANGE_RATE_FRACTION_DIGITS
rate2Edit.fractionDigits = EXCHANGE_RATE_FRACTION_DIGITS
rate1Edit.addTextChangedListener(LinkedExchangeRateTextWatcher(true))
Expand Down Expand Up @@ -139,15 +138,25 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou
}

fun setCurrencies(first: CurrencyUnit?, second: CurrencyUnit?) {
if (first != null) {
firstCurrency = first
first?.let {
firstCurrency = it
}
if (second != null) {
secondCurrency = second
second?.let {
secondCurrency = it
}
if (firstCurrency != null && secondCurrency != null) {
setSymbols(binding.ExchangeRate1, firstCurrency!!.symbol, secondCurrency!!.symbol)
setSymbols(binding.ExchangeRate2, secondCurrency!!.symbol, firstCurrency!!.symbol)
if (::firstCurrency.isInitialized && ::secondCurrency.isInitialized) {
setSymbols(binding.ExchangeRate1, firstCurrency.symbol, secondCurrency.symbol)
setSymbols(binding.ExchangeRate2, secondCurrency.symbol, firstCurrency.symbol)
rate1Edit.setHintForA11yOnly(context.getString(
R.string.content_description_exchange_rate,
firstCurrency.description,
secondCurrency.description
))
rate2Edit.setHintForA11yOnly(context.getString(
R.string.content_description_exchange_rate,
secondCurrency.description,
firstCurrency.description
))
}
}

Expand All @@ -156,11 +165,12 @@ class ExchangeRateEdit(context: Context, attrs: AttributeSet?) : ConstraintLayou
group.ExchangeRateLabel2.text = symbol2
}


private inner class LinkedExchangeRateTextWatcher(
/**
* true if we are linked to exchange rate where unit is from account currency
*/
private val isMain: Boolean
private val isMain: Boolean,
) : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.totschnig.myexpenses.util.ui

import app.futured.donut.DonutSection

data class DisplayProgress(val displayValue: Float, val displayExcess: Float) {
fun forViewSystem(valueColor: Int, excessColor: Int) = listOf(
DonutSection("excess", excessColor, displayExcess),
DonutSection("progress", valueColor, displayValue)
)

fun forCompose(
valueColor: androidx.compose.ui.graphics.Color,
excessColor: androidx.compose.ui.graphics.Color,
) = listOf(
app.futured.donut.compose.data.DonutSection(displayExcess, excessColor),
app.futured.donut.compose.data.DonutSection(displayValue, valueColor)
)
companion object {
fun calcProgressVisualRepresentation(progress: Float) = when {

progress > 200 -> DisplayProgress(0f,100f)

progress > 100 -> DisplayProgress(200f - progress, progress - 100)

progress >= 0 -> DisplayProgress(progress, 0f)

else -> throw IllegalArgumentException()
}
}
}
Loading

0 comments on commit ec9d9e4

Please sign in to comment.