Simplified MainScreen bottom bar implementation

This commit is contained in:
Juha Heinanen
2025-10-21 09:50:00 +03:00
parent ee98e890f5
commit 3afc40cfca
5 changed files with 75 additions and 136 deletions
@@ -223,7 +223,7 @@ private fun MainScreen(
val ua = UserAgent.ofAor(viewModel.selectedAor.value) val ua = UserAgent.ofAor(viewModel.selectedAor.value)
if (ua != null) { if (ua != null) {
showCall(ctx, viewModel, ua) showCall(ctx, viewModel, ua)
updateIcons(viewModel, ua.account) viewModel.triggerAccountUpdate()
} }
} }
Lifecycle.Event.ON_PAUSE -> { Lifecycle.Event.ON_PAUSE -> {
@@ -447,8 +447,7 @@ private fun MainScreen(
LaunchedEffect(currentRoute, viewModel.selectedAor.collectAsState()) { LaunchedEffect(currentRoute, viewModel.selectedAor.collectAsState()) {
if (currentRoute == "main") { if (currentRoute == "main") {
Log.d(TAG, "Updating icons for AOR: ${viewModel.selectedAor.value}") Log.d(TAG, "Updating icons for AOR: ${viewModel.selectedAor.value}")
val account = Account.ofAor(viewModel.selectedAor.value) viewModel.triggerAccountUpdate()
updateIcons(viewModel, account)
} }
} }
@@ -673,41 +672,56 @@ private fun TopAppBar(
@Composable @Composable
private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavController) { private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavController) {
val vmIcon by viewModel.vmIcon.collectAsState() // ... (the top part of your function is the same) ...
val showVmIcon by viewModel.showVmIcon.collectAsState()
val messagesIcon by viewModel.messagesIcon.collectAsState()
val callsIcon by viewModel.callsIcon.collectAsState()
val dialpadIcon by viewModel.dialpadIcon.collectAsState() val dialpadIcon by viewModel.dialpadIcon.collectAsState()
val aor by viewModel.selectedAor.collectAsState()
val accountUpdate by viewModel.accountUpdate.collectAsState()
val showVmIcon = remember(aor, accountUpdate) {
if (aor.isNotEmpty()) Account.ofAor(aor)?.vmUri?.isNotEmpty() ?: false else false
}
val hasNewVoicemail = remember(aor, accountUpdate) {
if (aor.isNotEmpty()) (Account.ofAor(aor)?.vmNew ?: 0) > 0 else false
}
val hasUnreadMessages = remember(aor, accountUpdate) {
if (aor.isNotEmpty()) Account.ofAor(aor)?.unreadMessages ?: false else false
}
val hasMissedCalls = remember(aor, accountUpdate) {
if (aor.isNotEmpty()) Account.ofAor(aor)?.missedCalls ?: false else false
}
val buttonSize = 48.dp val buttonSize = 48.dp
Row( modifier = Modifier Row(
.fillMaxWidth() modifier = Modifier
.navigationBarsPadding() .fillMaxWidth()
.padding(bottom = 16.dp), .navigationBarsPadding()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (showVmIcon) if (showVmIcon)
IconButton( IconButton(
// Disable the button if no account is selected
enabled = aor.isNotEmpty(),
onClick = { onClick = {
if (viewModel.selectedAor.value != "") { // No need for an 'if' check here anymore
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!! val ua = UserAgent.ofAor(aor)!!
val acc = ua.account val acc = ua.account
if (acc.vmUri != "") { if (acc.vmUri.isNotEmpty()) {
dialogTitle.value = ctx.getString(R.string.voicemail_messages) dialogTitle.value = ctx.getString(R.string.voicemail_messages)
dialogMessage.value = acc.vmMessages(ctx) dialogMessage.value = acc.vmMessages(ctx)
positiveText.value = ctx.getString(R.string.listen) positiveText.value = ctx.getString(R.string.listen)
onPositiveClicked.value = { onPositiveClicked.value = {
val intent = Intent(ctx, MainActivity::class.java) val intent = Intent(ctx, MainActivity::class.java)
intent.putExtra("uap", ua.uap) intent.putExtra("uap", ua.uap)
intent.putExtra("peer", acc.vmUri) intent.putExtra("peer", acc.vmUri)
handleIntent(ctx, viewModel, intent, "call") handleIntent(ctx, viewModel, intent, "call")
}
negativeText.value = ctx.getString(R.string.cancel)
onNegativeClicked.value = {}
showDialog.value = true
} }
negativeText.value = ctx.getString(R.string.cancel)
onNegativeClicked.value = {}
showDialog.value = true
} }
}, },
modifier = Modifier modifier = Modifier
@@ -715,13 +729,10 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
.size(buttonSize) .size(buttonSize)
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(vmIcon), imageVector = ImageVector.vectorResource(R.drawable.voicemail),
contentDescription = null, contentDescription = null,
Modifier.size(buttonSize), Modifier.size(buttonSize),
tint = if (vmIcon == R.drawable.voicemail) tint = if (hasNewVoicemail) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.error
) )
} }
@@ -740,52 +751,49 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
} }
IconButton( IconButton(
// Disable the button if no account is selected
enabled = aor.isNotEmpty(),
onClick = { onClick = {
if (viewModel.selectedAor.value != "") // No need for an 'if' check here anymore
navController.navigate("chats/${viewModel.selectedAor.value}") navController.navigate("chats/$aor")
}, },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.size(buttonSize) .size(buttonSize)
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(messagesIcon), imageVector = ImageVector.vectorResource(R.drawable.messages),
contentDescription = null, contentDescription = null,
Modifier.size(buttonSize), Modifier.size(buttonSize),
tint = if (messagesIcon == R.drawable.messages) tint = if (hasUnreadMessages) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.error
) )
} }
IconButton( IconButton(
// Disable the button if no account is selected
enabled = aor.isNotEmpty(),
onClick = { onClick = {
if (viewModel.selectedAor.value != "") // No need for an 'if' check here anymore
navController.navigate("calls/${viewModel.selectedAor.value}") navController.navigate("calls/$aor")
}, },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.size(buttonSize) .size(buttonSize)
) { ) {
Icon( Icon(
imageVector = ImageVector.vectorResource(callsIcon), imageVector = ImageVector.vectorResource(R.drawable.calls),
contentDescription = null, contentDescription = null,
Modifier.size(buttonSize), Modifier.size(buttonSize),
tint = if (callsIcon == R.drawable.calls) tint = if (hasMissedCalls) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.error
) )
} }
IconButton( IconButton(
onClick = { viewModel.updateDialpadIcon( onClick = {
if (dialpadIcon == R.drawable.dialpad_off) viewModel.updateDialpadIcon(
R.drawable.dialpad_on if (dialpadIcon == R.drawable.dialpad_off) R.drawable.dialpad_on else R.drawable.dialpad_off
else )
R.drawable.dialpad_off },
) },
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.size(buttonSize), .size(buttonSize),
@@ -795,14 +803,10 @@ private fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavCont
imageVector = ImageVector.vectorResource(dialpadIcon), imageVector = ImageVector.vectorResource(dialpadIcon),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(buttonSize), modifier = Modifier.size(buttonSize),
tint = if (dialpadIcon == R.drawable.dialpad_off) tint = if (dialpadIcon == R.drawable.dialpad_off) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.error
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.error
) )
} }
} }
} }
private val callUri = mutableStateOf("") private val callUri = mutableStateOf("")
@@ -973,8 +977,7 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
} }
showCall(ctx, viewModel, UserAgent.ofAor(selected)) showCall(ctx, viewModel, UserAgent.ofAor(selected))
viewModel.triggerAccountUpdate()
updateIcons(viewModel, Account.ofAor(selected))
if (selected == "") { if (selected == "") {
OutlinedButton( OutlinedButton(
@@ -1103,7 +1106,7 @@ private fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: Na
expanded = false expanded = false
viewModel.updateSelectedAor(acc.aor) viewModel.updateSelectedAor(acc.aor)
showCall(ctx, viewModel, ua) showCall(ctx, viewModel, ua)
updateIcons(viewModel, acc) viewModel.triggerAccountUpdate()
}, },
text = { Text( text = { Text(
text = acc.text(), text = acc.text(),
@@ -1831,7 +1834,7 @@ private fun OnHoldNotice() {
private fun spinToAor(viewModel: ViewModel, aor: String) { private fun spinToAor(viewModel: ViewModel, aor: String) {
if (aor != viewModel.selectedAor.value) if (aor != viewModel.selectedAor.value)
viewModel.updateSelectedAor(aor) viewModel.updateSelectedAor(aor)
updateIcons(viewModel, Account.ofAor(aor)) viewModel.triggerAccountUpdate()
} }
private fun callClick(ctx: Context, viewModel: ViewModel) { private fun callClick(ctx: Context, viewModel: ViewModel) {
@@ -2193,7 +2196,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
when (ev[0]) { when (ev[0]) {
"call rejected" -> { "call rejected" -> {
if (aor == viewModel.selectedAor.value) if (aor == viewModel.selectedAor.value)
viewModel.updateCallsIcon(R.drawable.calls_missed) viewModel.triggerAccountUpdate()
} }
"call incoming", "call outgoing" -> { "call incoming", "call outgoing" -> {
val callp = params[1] as Long val callp = params[1] as Long
@@ -2336,7 +2339,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
ua.account.resumeUri = "" ua.account.resumeUri = ""
showCall(ctx, viewModel, ua) showCall(ctx, viewModel, ua)
if (acc.missedCalls) if (acc.missedCalls)
viewModel.updateCallsIcon(R.drawable.calls_missed) viewModel.triggerAccountUpdate()
} }
//if (kgm.isDeviceLocked) //if (kgm.isDeviceLocked)
// this.setShowWhenLocked(false) // this.setShowWhenLocked(false)
@@ -2356,13 +2359,8 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
break break
} }
} }
if (aor == viewModel.selectedAor.value) { if (aor == viewModel.selectedAor.value)
viewModel.updateVmIcon(if (acc.vmNew > 0) viewModel.triggerAccountUpdate()
R.drawable.voicemail_new
else
R.drawable.voicemail
)
}
} }
else -> Log.e(TAG, "Unknown event '${ev[0]}'") else -> Log.e(TAG, "Unknown event '${ev[0]}'")
} }
@@ -2536,36 +2534,6 @@ private fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, ca
} }
} }
private fun updateIcons(viewModel: ViewModel, acc: Account?) {
if (acc == null) {
viewModel.updateShowVmIcon(false)
viewModel.updateMessagesIcon(R.drawable.messages)
viewModel.updateCallsIcon(R.drawable.calls)
}
else {
if (acc.vmUri != "") {
viewModel.updateShowVmIcon(true)
viewModel.updateVmIcon(if (acc.vmNew > 0)
R.drawable.voicemail_new
else
R.drawable.voicemail
)
} else
viewModel.updateShowVmIcon(false)
viewModel.updateMessagesIcon(if (acc.unreadMessages)
R.drawable.messages_unread
else
R.drawable.messages)
viewModel.updateCallsIcon(if (acc.missedCalls)
R.drawable.calls_missed
else
R.drawable.calls)
}
}
private fun backup(ctx: Context, password: String) { private fun backup(ctx: Context, password: String) {
val files = arrayListOf("accounts", "config", "contacts", "call_history", val files = arrayListOf("accounts", "config", "contacts", "call_history",
"messages", "uuid", "gzrtp.zid", "cert.pem", "ca_cert", "ca_certs.crt") "messages", "uuid", "gzrtp.zid", "cert.pem", "ca_cert", "ca_certs.crt")
@@ -30,6 +30,13 @@ class ViewModel(application: Application) : AndroidViewModel(application) {
_selectedAor.value = newValue _selectedAor.value = newValue
} }
private val _accountUpdate = MutableStateFlow(0)
val accountUpdate: StateFlow<Int> = _accountUpdate
fun triggerAccountUpdate() {
_accountUpdate.value++
}
private val _aorPeerMessage = MutableStateFlow(mutableMapOf<AorPeer, String>()) private val _aorPeerMessage = MutableStateFlow(mutableMapOf<AorPeer, String>())
fun updateAorPeerMessage(aor: String, peerUri: String, message: String) { fun updateAorPeerMessage(aor: String, peerUri: String, message: String) {
@@ -73,13 +80,6 @@ class ViewModel(application: Application) : AndroidViewModel(application) {
_micIcon.value = iconResId _micIcon.value = iconResId
} }
private val _vmIcon = MutableStateFlow(R.drawable.voicemail)
val vmIcon: StateFlow<Int> = _vmIcon.asStateFlow()
fun updateVmIcon(newIcon: Int) {
_vmIcon.value = newIcon
}
private val _showVmIcon = MutableStateFlow(false) private val _showVmIcon = MutableStateFlow(false)
val showVmIcon: StateFlow<Boolean> = _showVmIcon.asStateFlow() val showVmIcon: StateFlow<Boolean> = _showVmIcon.asStateFlow()
@@ -87,20 +87,6 @@ class ViewModel(application: Application) : AndroidViewModel(application) {
_showVmIcon.value = show _showVmIcon.value = show
} }
private val _messagesIcon = MutableStateFlow(R.drawable.messages)
val messagesIcon: StateFlow<Int> = _messagesIcon.asStateFlow()
fun updateMessagesIcon(newIcon: Int) {
_messagesIcon.value = newIcon
}
private val _callsIcon = MutableStateFlow(R.drawable.calls)
val callsIcon: StateFlow<Int> = _callsIcon.asStateFlow()
fun updateCallsIcon(newIcon: Int) {
_callsIcon.value = newIcon
}
private val _dialpadIcon = MutableStateFlow(R.drawable.dialpad_off) private val _dialpadIcon = MutableStateFlow(R.drawable.dialpad_off)
val dialpadIcon: StateFlow<Int> = _dialpadIcon.asStateFlow() val dialpadIcon: StateFlow<Int> = _dialpadIcon.asStateFlow()
@@ -1,5 +0,0 @@
<vector android:height="48dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>
@@ -1,5 +0,0 @@
<vector android:height="44dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="44dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
</vector>
@@ -1,5 +0,0 @@
<vector android:height="48dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M18.5,6C15.46,6 13,8.46 13,11.5c0,1.33 0.47,2.55 1.26,3.5L9.74,15c0.79,-0.95 1.26,-2.17 1.26,-3.5C11,8.46 8.54,6 5.5,6S0,8.46 0,11.5 2.46,17 5.5,17h13c3.04,0 5.5,-2.46 5.5,-5.5S21.54,6 18.5,6zM5.5,15C3.57,15 2,13.43 2,11.5S3.57,8 5.5,8 9,9.57 9,11.5 7.43,15 5.5,15zM18.5,15c-1.93,0 -3.5,-1.57 -3.5,-3.5S16.57,8 18.5,8 22,9.57 22,11.5 20.43,15 18.5,15z"/>
</vector>