Coverage Summary for Class: MacroGramsTextFieldKt (com.opty.numpad)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| MacroGramsTextFieldKt |
0%
(0/10)
|
0%
(0/76)
|
0%
(0/87)
|
0%
(0/1187)
|
| MacroGramsTextFieldKt$MacroGramsTextField$10$1 |
0%
(0/2)
|
|
0%
(0/3)
|
0%
(0/19)
|
| MacroGramsTextFieldKt$MacroGramsTextField$4$1 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/3)
|
0%
(0/18)
|
| Total |
0%
(0/13)
|
0%
(0/80)
|
0%
(0/93)
|
0%
(0/1224)
|
/*
* Copyright 2024 Opty All rights reserved.
*
* This software and associated documentation files (the "Software") are protected by copyright law and
* international treaties. This software has been licensed to users under the terms of the End User
* License Agreement (EULA) and Terms of Service (ToS) available within the application.
*
* Unauthorized copying, reproduction, modification, distribution, display, performance, sublicense, sale,
* access or use of this Software, or any part thereof, is strictly prohibited and will be prosecuted
* under applicable laws.
*
* The Software contains valuable trade secrets and proprietary information of Opty.
* No part of the Software may be reproduced, distributed, or transmitted in any form or by any means,
* including photocopying, recording, or other electronic or mechanical methods without the prior written
* permission of Opty.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* For licensing inquiries, please contact: support@optymacros.com
*/
package com.opty.numpad
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.opty.common.decimalSeparator
import com.opty.designsystem.components.Text
import com.opty.designsystem.components.textfield.TextField
import com.opty.designsystem.components.textfield.expandWidthCentered
import com.opty.designsystem.components.textfield.macroTextFieldColors
private const val MINIMUM_WIDTH_QUANTITY_FIELD = 59f
private const val CORNER_RADIUS = 25
private const val TEXT_FIELD_START_PADDING = 10
private const val TEXT_FIELD_VERTICAL_PADDING = 2
private const val EXTRA_WIDTH_QUANTITY_FIELD = 2
private const val TEXT_FIELD_MIN_HEIGHT = 28
private val contentPaddingValues =
PaddingValues(
start = TEXT_FIELD_START_PADDING.dp,
end = 0.dp,
top = TEXT_FIELD_VERTICAL_PADDING.dp,
bottom = TEXT_FIELD_VERTICAL_PADDING.dp,
)
@Composable
fun MacroGramsTextField(
textFieldStartG: Int,
setValue: (Float) -> Unit,
textStyle: TextStyle,
enabled: Boolean,
modifier: Modifier = Modifier,
testTag: String? = null,
readOnly: Boolean = false,
onDoneClick: () -> Unit = {},
onFocusGain: () -> Unit = {},
onClickWhenDisable: () -> Unit = {},
suffixRightPadding: Float = 0f,
suffixLeftPadding: Float = 0f,
minWidth: Float = MINIMUM_WIDTH_QUANTITY_FIELD,
trailingIcon: @Composable (() -> Unit)? = null,
) {
var waitingFirstFocus by remember { mutableStateOf(false) }
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }
val textFieldValue = TextFieldValue(textFieldStartG.toString(), TextRange(textFieldStartG.toString().length))
val numpadController = LocalNumpadController.current
val numpadOwner = remember { Any() }
val useEmbeddedNumpad = numpadController != null && enabled && !readOnly
LaunchedEffect(waitingFirstFocus, enabled) {
if (enabled && waitingFirstFocus) {
focusRequester.requestFocus()
waitingFirstFocus = false
}
}
TextField(
readOnly = readOnly || useEmbeddedNumpad,
contentPaddingValues = contentPaddingValues,
trailingIcon = trailingIcon,
value = textFieldValue,
onValueChange = { newTextFieldValue ->
onGramsValueChange(newTextFieldValue, setValue)
},
textStyle = remember(textStyle) { textStyle.copy(textAlign = TextAlign.Center) },
keyboardOptions =
remember(useEmbeddedNumpad) {
KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done,
showKeyboardOnFocus = !useEmbeddedNumpad,
)
},
keyboardActions =
remember(onDoneClick) {
KeyboardActions(
onDone = {
keyboardController?.hide()
onDoneClick()
},
)
},
suffix = {
Box(modifier = Modifier.padding(end = suffixRightPadding.dp, start = suffixLeftPadding.dp)) {
Text("g", style = textStyle)
}
},
modifier =
modifier
.focusRequester(focusRequester)
.width(IntrinsicSize.Min)
.onFocusChanged { focusState ->
if (focusState.isFocused) {
onFocusGain()
if (useEmbeddedNumpad) {
keyboardController?.hide()
numpadController.show(
owner = numpadOwner,
value = textFieldValue,
spec =
NumpadInputSpec(
maxValue = Int.MAX_VALUE.toDouble(),
decimalSeparator = decimalSeparator,
maxFractionDigits = 0,
),
onValueChange = { onGramsValueChange(it, setValue) },
onDone = onDoneClick,
)
}
} else {
numpadController?.dismiss(numpadOwner)
}
}.expandWidthCentered(EXTRA_WIDTH_QUANTITY_FIELD, minWidth, true)
.let { textFieldModifier ->
if (testTag != null) {
textFieldModifier.testTag(testTag)
} else {
textFieldModifier
}
}
.pointerInput(Unit) {
detectTapGestures(
onTap = {
waitingFirstFocus = true
onClickWhenDisable()
},
)
},
shape = RoundedCornerShape(CORNER_RADIUS.dp),
minHeight = TEXT_FIELD_MIN_HEIGHT.dp,
colors = macroTextFieldColors(),
singleLine = true,
enabled = enabled,
)
}
private fun onGramsValueChange(
newTextFieldValue: TextFieldValue,
setValue: (Float) -> Unit,
) {
val safeString =
buildString {
append("0")
newTextFieldValue.text.forEach { append(if (it.isDigit()) it else "") }
}
safeString.toIntOrNull()?.let {
setValue(it.toFloat())
}
}