Improved mainscreen call row layout

This commit is contained in:
Juha Heinanen
2026-05-10 16:12:23 +03:00
parent eb18baa089
commit 4588dabb41
@@ -1472,7 +1472,8 @@ private fun CallRow(
Row( modifier = Modifier Row( modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center horizontalArrangement = if (isDialer || call?.showCancelButton?.value == true || call?.showAnswerRejectButtons?.value == true)
Arrangement.Center else Arrangement.SpaceBetween
) { ) {
if (isDialer) { if (isDialer) {
if (dialerState.showCallButton.value) if (dialerState.showCallButton.value)
@@ -1524,8 +1525,6 @@ private fun CallRow(
} }
else { else {
if (call!!.showCancelButton.value) { if (call!!.showCancelButton.value) {
if (!call.conferenceCall)
Spacer(modifier = Modifier.weight(1f))
IconButton( IconButton(
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
enabled = !call.terminated.value, enabled = !call.terminated.value,
@@ -1543,11 +1542,9 @@ private fun CallRow(
contentDescription = null, contentDescription = null,
) )
} }
Spacer(modifier = Modifier.width(12.dp))
} }
if (call.showHangupButton.value) { if (call.showHangupButton.value) {
IconButton( IconButton(
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
enabled = !call.terminated.value, enabled = !call.terminated.value,
@@ -1565,311 +1562,313 @@ private fun CallRow(
contentDescription = null, contentDescription = null,
) )
} }
}
if (!call.conferenceCall) if (call.showHangupButton.value && !call.conferenceCall)
IconButton( modifier = Modifier.size(48.dp), IconButton( modifier = Modifier.size(48.dp),
onClick = { onClick = {
if (call.callOnHold.value) { if (call.callOnHold.value) {
Log.d(TAG, "User requested resume for ${call.callp}") Log.d(TAG, "User requested resume for ${call.callp}")
call.resume() // This now automatically holds other calls call.resume() // This now automatically holds other calls
} else { } else {
Log.d(TAG, "User requested hold for ${call.callp}") Log.d(TAG, "User requested hold for ${call.callp}")
call.hold() call.hold()
}
},
) {
Icon(
imageVector = Icons.Outlined.PauseCircle,
modifier = Modifier.size(42.dp),
tint = if (call.callOnHold.value)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary,
contentDescription = null,
)
}
var showTransferDialog by remember { mutableStateOf(false) }
if (call.showHangupButton.value && !call.conferenceCall && !call.ua.account.isMobile)
IconButton(
modifier = Modifier.size(48.dp),
enabled = call.transferButtonEnabled.value,
onClick = {
if (call.onHoldCall != null) {
if (!Api.call_supported(call.callp, Api.REPLACES)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.replaces_not_supported)
showAlert.value = true
} }
}, else {
) { call.hold()
Icon( if (!call.executeTransfer()) {
imageVector = Icons.Outlined.PauseCircle, alertTitle.value = ctx.getString(R.string.notice)
modifier = Modifier.size(42.dp), alertMessage.value = ctx.getString(R.string.transfer_failed)
tint = if (call.callOnHold.value)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary,
contentDescription = null,
)
}
var showTransferDialog by remember { mutableStateOf(false) }
if (!call.conferenceCall)
IconButton(
modifier = Modifier.size(48.dp),
enabled = call.transferButtonEnabled.value,
onClick = {
if (call.onHoldCall != null) {
if (!Api.call_supported(call.callp, Api.REPLACES)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.replaces_not_supported)
showAlert.value = true showAlert.value = true
} }
else { }
call.hold() }
if (!call.executeTransfer()) { else
alertTitle.value = ctx.getString(R.string.notice) showTransferDialog = true
alertMessage.value = ctx.getString(R.string.transfer_failed) },
showAlert.value = true ) {
Icon(
imageVector = Icons.Outlined.ArrowCircleRight,
modifier = Modifier.size(42.dp),
tint = if (call.callTransfer.value)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary,
contentDescription = null,
)
}
if (showTransferDialog) {
val showDialog = remember { mutableStateOf(true) }
val blindChecked = remember { mutableStateOf(true) }
if (showDialog.value)
BasicAlertDialog(
onDismissRequest = {
viewModel.requestHideKeyboard()
showDialog.value = false
showTransferDialog = false
}
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = stringResource(R.string.call_transfer),
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onSurface,
)
var transferUri by remember { mutableStateOf("") }
val suggestions by remember { contactNames }
var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }
val focusRequester = remember { FocusRequester() }
val lazyListState = rememberLazyListState()
OutlinedTextField(
value = transferUri,
singleLine = true,
onValueChange = {
if (it != transferUri) {
transferUri = it
if (it.length > 1) {
val normalizedInput = Utils.unaccent(it)
filteredSuggestions =
suggestions.filter { suggestion ->
Utils.unaccent(suggestion)
.contains(normalizedInput, ignoreCase = true)
}
.map { suggestion ->
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
}
}
call.showSuggestions.value = transferUri.length > 1
}
},
trailingIcon = {
if (transferUri.isNotEmpty())
Icon(
Icons.Outlined.Clear,
contentDescription = null,
modifier = Modifier.clickable {
if (call.showSuggestions.value)
call.showSuggestions.value = false
else
transferUri = ""
},
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = 4.dp,
end = 4.dp,
top = 12.dp,
bottom = 2.dp
)
.focusRequester(focusRequester),
label = { Text(stringResource(R.string.transfer_destination)) },
textStyle = TextStyle(fontSize = 18.sp),
keyboardOptions = if (isDialpadVisible)
KeyboardOptions(keyboardType = KeyboardType.Phone)
else
KeyboardOptions(keyboardType = KeyboardType.Text)
)
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.shadow(8.dp, RoundedCornerShape(8.dp))
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(8.dp)
)
.animateContentSize()
) {
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
Box(modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.verticalScrollbar(
state = lazyListState,
width = 6.dp,
),
horizontalAlignment = Alignment.Start,
state = lazyListState,
) {
items(
items = filteredSuggestions,
key = { suggestion -> suggestion.toString() }
) { suggestion ->
Box(
modifier = Modifier
.fillMaxWidth()
.clickable {
transferUri =
suggestion.toString()
call.showSuggestions.value =
false
}
.padding(12.dp)
) {
Text(
text = suggestion,
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 18.sp
)
}
}
}
}
} }
} }
} if (call.replaces())
else
showTransferDialog = true
},
) {
Icon(
imageVector = Icons.Outlined.ArrowCircleRight,
modifier = Modifier.size(42.dp),
tint = if (call.callTransfer.value)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.secondary,
contentDescription = null,
)
}
if (showTransferDialog) {
val showDialog = remember { mutableStateOf(true) }
val blindChecked = remember { mutableStateOf(true) }
if (showDialog.value)
BasicAlertDialog(
onDismissRequest = {
viewModel.requestHideKeyboard()
showDialog.value = false
showTransferDialog = false
}
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = stringResource(R.string.call_transfer),
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onSurface,
)
var transferUri by remember { mutableStateOf("") }
val suggestions by remember { contactNames }
var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }
val focusRequester = remember { FocusRequester() }
val lazyListState = rememberLazyListState()
OutlinedTextField(
value = transferUri,
singleLine = true,
onValueChange = {
if (it != transferUri) {
transferUri = it
if (it.length > 1) {
val normalizedInput = Utils.unaccent(it)
filteredSuggestions =
suggestions.filter { suggestion ->
Utils.unaccent(suggestion)
.contains(normalizedInput, ignoreCase = true)
}
.map { suggestion ->
Utils.buildAnnotatedStringWithHighlight(suggestion, it)
}
}
call.showSuggestions.value = transferUri.length > 1
}
},
trailingIcon = {
if (transferUri.isNotEmpty())
Icon(
Icons.Outlined.Clear,
contentDescription = null,
modifier = Modifier.clickable {
if (call.showSuggestions.value)
call.showSuggestions.value = false
else
transferUri = ""
},
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
modifier = Modifier
.fillMaxWidth()
.padding(
start = 4.dp,
end = 4.dp,
top = 12.dp,
bottom = 2.dp
)
.focusRequester(focusRequester),
label = { Text(stringResource(R.string.transfer_destination)) },
textStyle = TextStyle(fontSize = 18.sp),
keyboardOptions = if (isDialpadVisible)
KeyboardOptions(keyboardType = KeyboardType.Phone)
else
KeyboardOptions(keyboardType = KeyboardType.Text)
)
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.shadow(8.dp, RoundedCornerShape(8.dp))
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(8.dp)
)
.animateContentSize()
) {
if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {
Box(modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.verticalScrollbar(
state = lazyListState,
width = 6.dp,
),
horizontalAlignment = Alignment.Start,
state = lazyListState,
) {
items(
items = filteredSuggestions,
key = { suggestion -> suggestion.toString() }
) { suggestion ->
Box(
modifier = Modifier
.fillMaxWidth()
.clickable {
transferUri =
suggestion.toString()
call.showSuggestions.value =
false
}
.padding(12.dp)
) {
Text(
text = suggestion,
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 18.sp
)
}
}
}
}
}
}
if (call.replaces())
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.blind),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(8.dp),
)
Switch(
checked = blindChecked.value,
onCheckedChange = {
blindChecked.value = true
}
)
}
Spacer(modifier = Modifier.width(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.attended),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(8.dp),
)
Switch(
checked = !blindChecked.value,
onCheckedChange = {
blindChecked.value = false
}
)
}
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) { ) {
TextButton( Row(verticalAlignment = Alignment.CenterVertically) {
onClick = {
viewModel.requestHideKeyboard()
showDialog.value = false
showTransferDialog = false
},
modifier = Modifier.padding(end = 32.dp),
) {
Text( Text(
text = stringResource(R.string.cancel), text = stringResource(R.string.blind),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(8.dp),
)
Switch(
checked = blindChecked.value,
onCheckedChange = {
blindChecked.value = true
}
) )
} }
TextButton( Spacer(modifier = Modifier.width(8.dp))
onClick = { Row(verticalAlignment = Alignment.CenterVertically) {
call.showSuggestions.value = false Text(
var uriText = transferUri.trim() text = stringResource(R.string.attended),
if (uriText.isNotEmpty()) { color = MaterialTheme.colorScheme.onSurfaceVariant,
val uris = Contact.contactUris(uriText) modifier = Modifier.padding(8.dp),
if (uris.size > 1) { )
selectItems.value = uris Switch(
selectItemAction.value = { index -> checked = !blindChecked.value,
val uri = uris[index] onCheckedChange = {
transfer( blindChecked.value = false
ctx, }
viewModel, )
call.ua, }
if (Utils.isTelNumber(uri)) "tel:$uri" else uri, }
!blindChecked.value Row(
) modifier = Modifier.fillMaxWidth(),
showSelectItemDialog.value = false horizontalArrangement = Arrangement.End,
} verticalAlignment = Alignment.CenterVertically
showSelectItemDialog.value = true ) {
} TextButton(
else { onClick = {
if (uris.size == 1) uriText = uris[0] viewModel.requestHideKeyboard()
showDialog.value = false
showTransferDialog = false
},
modifier = Modifier.padding(end = 32.dp),
) {
Text(
text = stringResource(R.string.cancel),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
TextButton(
onClick = {
call.showSuggestions.value = false
var uriText = transferUri.trim()
if (uriText.isNotEmpty()) {
val uris = Contact.contactUris(uriText)
if (uris.size > 1) {
selectItems.value = uris
selectItemAction.value = { index ->
val uri = uris[index]
transfer( transfer(
ctx, ctx,
viewModel, viewModel,
call.ua, call.ua,
if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText, if (Utils.isTelNumber(uri)) "tel:$uri" else uri,
!blindChecked.value !blindChecked.value
) )
showSelectItemDialog.value = false
} }
viewModel.requestHideKeyboard() showSelectItemDialog.value = true
showDialog.value = false
showTransferDialog = false
} }
}, else {
modifier = Modifier.padding(end = 16.dp), if (uris.size == 1) uriText = uris[0]
) { transfer(
Text( ctx,
text = stringResource( viewModel,
if (blindChecked.value) call.ua,
R.string.transfer if (Utils.isTelNumber(uriText)) "tel:$uriText" else uriText,
else !blindChecked.value
R.string.call )
).uppercase(), }
color = MaterialTheme.colorScheme.primary viewModel.requestHideKeyboard()
) showDialog.value = false
} showTransferDialog = false
}
},
modifier = Modifier.padding(end = 16.dp),
) {
Text(
text = stringResource(
if (blindChecked.value)
R.string.transfer
else
R.string.call
).uppercase(),
color = MaterialTheme.colorScheme.primary
)
} }
} }
} }
} }
}
} }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val shouldRequestFocus by call.focusDtmf val shouldRequestFocus by call.focusDtmf
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
if (call.showHangupButton.value)
BasicTextField( BasicTextField(
value = call.dtmfText.value, value = call.dtmfText.value,
onValueChange = { newText -> onValueChange = { newText ->
@@ -1924,6 +1923,7 @@ private fun CallRow(
) )
} }
) )
if (call.showHangupButton.value)
LaunchedEffect(shouldRequestFocus) { LaunchedEffect(shouldRequestFocus) {
if (shouldRequestFocus) { if (shouldRequestFocus) {
focusRequester.requestFocus() focusRequester.requestFocus()
@@ -1931,6 +1931,7 @@ private fun CallRow(
} }
} }
if (call.showHangupButton.value && !call.ua.account.isMobile)
IconButton( IconButton(
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
onClick = { onClick = {
@@ -1970,7 +1971,6 @@ private fun CallRow(
contentDescription = null, contentDescription = null,
) )
} }
}
if (call.showAnswerRejectButtons.value) { if (call.showAnswerRejectButtons.value) {