Restore new message after pause/resume

This commit is contained in:
Juha Heinanen
2025-04-30 13:20:41 +03:00
parent 990bb0fba4
commit b8c2f80414
2 changed files with 105 additions and 60 deletions
@@ -61,25 +61,31 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
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 androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.tutpro.baresip.CustomElements.AlertDialog import com.tutpro.baresip.CustomElements.AlertDialog
import com.tutpro.baresip.CustomElements.EventListener
import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.LabelText
import com.tutpro.baresip.CustomElements.verticalScrollbar import com.tutpro.baresip.CustomElements.verticalScrollbar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -99,7 +105,6 @@ class ChatActivity : ComponentActivity() {
private var chatMessages : List<Message> by _chatMessages private var chatMessages : List<Message> by _chatMessages
private var focus = false private var focus = false
private var lastCall: Long = 0 private var lastCall: Long = 0
private var unsentMessage = ""
private var keyboardController: SoftwareKeyboardController? = null private var keyboardController: SoftwareKeyboardController? = null
private var backInvokedCallback: OnBackInvokedCallback? = null private var backInvokedCallback: OnBackInvokedCallback? = null
@@ -267,7 +272,9 @@ class ChatActivity : ComponentActivity() {
aor.split(":")[1] aor.split(":")[1]
Text( Text(
text = headerText, text = headerText,
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp), modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.dp),
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -406,8 +413,10 @@ class ChatActivity : ComponentActivity() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(start = if (message.direction == MESSAGE_DOWN) 0.dp else 24.dp, .padding(
end = if (message.direction == MESSAGE_DOWN) 24.dp else 0.dp) start = if (message.direction == MESSAGE_DOWN) 0.dp else 24.dp,
end = if (message.direction == MESSAGE_DOWN) 24.dp else 0.dp
)
) { ) {
Column { Column {
Row { Row {
@@ -449,6 +458,36 @@ class ChatActivity : ComponentActivity() {
@Composable @Composable
fun NewMessage(ctx: Context, peerUri: String) { fun NewMessage(ctx: Context, peerUri: String) {
var newMessage = rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(""))
}
var textFieldLoaded by remember { mutableStateOf(false) }
EventListener {
when (it) {
Lifecycle.Event.ON_RESUME -> {
val chatText = BaresipService.chatTexts["$aor::$peerUri"]
if (chatText != null) {
Log.d(TAG, "Restoring newMessage '$chatText' for $aor::$peerUri")
newMessage.value = TextFieldValue(
text = chatText,
selection = TextRange(chatText.length)
)
BaresipService.chatTexts.remove("$aor::$peerUri")
}
_chatMessages.value = uaPeerMessages(aor, peerUri)
}
Lifecycle.Event.ON_PAUSE -> {
if (newMessage.value != TextFieldValue("")) {
Log.d(TAG, "Saving newMessage '${newMessage.value.text}' for $aor::$peerUri")
BaresipService.chatTexts["$aor::$peerUri"] = newMessage.value.text
}
MainActivity.activityAor = aor
}
else -> {}
}
}
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
val dialogMessage = remember { mutableStateOf("") } val dialogMessage = remember { mutableStateOf("") }
@@ -459,7 +498,6 @@ class ChatActivity : ComponentActivity() {
positiveButtonText = stringResource(R.string.ok), positiveButtonText = stringResource(R.string.ok),
) )
var newMessage by remember { mutableStateOf("") }
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.navigationBarsPadding() .navigationBarsPadding()
@@ -468,16 +506,27 @@ class ChatActivity : ComponentActivity() {
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
OutlinedTextField( OutlinedTextField(
value = newMessage, value = newMessage.value.text,
placeholder = { Text(stringResource(R.string.new_message)) }, placeholder = { Text(stringResource(R.string.new_message)) },
onValueChange = { newMessage = it }, onValueChange = {
newMessage.value = newMessage.value.copy(text = it)
},
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(end = 8.dp) .padding(end = 8.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.onGloballyPositioned {
if (!textFieldLoaded) {
textFieldLoaded = true
//val textLength = newMessage.value.text.length
//newMessage.value = newMessage.value.copy(
// selection = TextRange(textLength)
//)
}
}
.combinedClickable( .combinedClickable(
onClick = { onClick = {
val msgText = newMessage val msgText = newMessage.value.text
if (msgText.isNotEmpty()) { if (msgText.isNotEmpty()) {
keyboardController?.hide() keyboardController?.hide()
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
@@ -498,7 +547,8 @@ class ChatActivity : ComponentActivity() {
if (ua.account.telProvider == "") { if (ua.account.telProvider == "") {
dialogMessage.value = String.format( dialogMessage.value = String.format(
getString(R.string.no_telephony_provider), getString(R.string.no_telephony_provider),
Utils.plainAor(aor)) Utils.plainAor(aor)
)
showDialog.value = true showDialog.value = true
} else { } else {
msgUri = Utils.telToSip(peerUri, ua.account) msgUri = Utils.telToSip(peerUri, ua.account)
@@ -513,12 +563,14 @@ class ChatActivity : ComponentActivity() {
time.toString() time.toString()
) != 0 ) != 0
) { ) {
Toast.makeText(ctx, "${getString(R.string.message_failed)}!", Toast.LENGTH_SHORT).show() Toast.makeText(
ctx, "${getString(R.string.message_failed)}!",
Toast.LENGTH_SHORT
).show()
msg.direction = MESSAGE_UP_FAIL msg.direction = MESSAGE_UP_FAIL
msg.responseReason = getString(R.string.message_failed) msg.responseReason = getString(R.string.message_failed)
} else { } else {
newMessage = "" newMessage.value = TextFieldValue("")
unsentMessage = ""
keyboardController?.hide() keyboardController?.hide()
BaresipService.chatTexts.remove("$aor::$peerUri") BaresipService.chatTexts.remove("$aor::$peerUri")
} }
@@ -531,7 +583,7 @@ class ChatActivity : ComponentActivity() {
val clipData = clipboardManager.primaryClip val clipData = clipboardManager.primaryClip
if (clipData != null && clipData.itemCount > 0) { if (clipData != null && clipData.itemCount > 0) {
val pastedText = clipData.getItemAt(0).text.toString() val pastedText = clipData.getItemAt(0).text.toString()
newMessage = pastedText newMessage.value = TextFieldValue(pastedText)
} else { } else {
Toast.makeText(ctx, "Nothing to paste", Toast.LENGTH_SHORT).show() Toast.makeText(ctx, "Nothing to paste", Toast.LENGTH_SHORT).show()
} }
@@ -539,11 +591,11 @@ class ChatActivity : ComponentActivity() {
), ),
singleLine = false, singleLine = false,
trailingIcon = { trailingIcon = {
if (newMessage.isNotEmpty()) { if (newMessage.value.text.isNotEmpty()) {
Icon( Icon(
Icons.Outlined.Clear, Icons.Outlined.Clear,
contentDescription = "Clear", contentDescription = "Clear",
modifier = Modifier.clickable { newMessage = "" } modifier = Modifier.clickable { newMessage.value = TextFieldValue("") }
) )
} }, } },
label = { LabelText(stringResource(R.string.new_message)) }, label = { LabelText(stringResource(R.string.new_message)) },
@@ -554,26 +606,21 @@ class ChatActivity : ComponentActivity() {
autoCorrectEnabled = true autoCorrectEnabled = true
) )
) )
LaunchedEffect(Unit) {
}
Image( Image(
painter = painterResource(id = R.drawable.send), painter = painterResource(id = R.drawable.send),
contentDescription = "Send", contentDescription = "Send",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.size(36.dp) modifier = Modifier
.size(36.dp)
.clickable { .clickable {
val msgText = newMessage val msgText = newMessage.value.text
if (msgText.isNotEmpty()) { if (msgText.isNotEmpty()) {
keyboardController?.hide() keyboardController?.hide()
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
val msg = Message( val msg =
aor, Message(aor, peerUri, msgText, time, MESSAGE_UP_WAIT, 0, "", true)
peerUri,
msgText,
time,
MESSAGE_UP_WAIT,
0,
"",
true
)
msg.add() msg.add()
var msgUri = "" var msgUri = ""
_chatMessages.value += msg _chatMessages.value += msg
@@ -581,7 +628,8 @@ class ChatActivity : ComponentActivity() {
if (ua.account.telProvider == "") { if (ua.account.telProvider == "") {
dialogMessage.value = String.format( dialogMessage.value = String.format(
getString(R.string.no_telephony_provider), getString(R.string.no_telephony_provider),
Utils.plainAor(aor)) Utils.plainAor(aor)
)
showDialog.value = true showDialog.value = true
} else { } else {
msgUri = Utils.telToSip(peerUri, ua.account) msgUri = Utils.telToSip(peerUri, ua.account)
@@ -589,19 +637,16 @@ class ChatActivity : ComponentActivity() {
else else
msgUri = peerUri msgUri = peerUri
if (msgUri != "") if (msgUri != "")
if (Api.message_send( if (Api.message_send(ua.uap, msgUri, msgText, time.toString()) != 0
ua.uap,
msgUri,
msgText,
time.toString()
) != 0
) { ) {
Toast.makeText(ctx, "${getString(R.string.message_failed)}!", Toast.LENGTH_SHORT).show() Toast.makeText(
ctx, "${getString(R.string.message_failed)}!",
Toast.LENGTH_SHORT
).show()
msg.direction = MESSAGE_UP_FAIL msg.direction = MESSAGE_UP_FAIL
msg.responseReason = getString(R.string.message_failed) msg.responseReason = getString(R.string.message_failed)
} else { } else {
newMessage = "" newMessage.value = TextFieldValue("")
unsentMessage = ""
keyboardController?.hide() keyboardController?.hide()
BaresipService.chatTexts.remove("$aor::$peerUri") BaresipService.chatTexts.remove("$aor::$peerUri")
} }
@@ -611,27 +656,6 @@ class ChatActivity : ComponentActivity() {
} }
} }
override fun onResume() {
super.onResume()
val chatText = BaresipService.chatTexts["$aor::$peerUri"]
if (chatText != null) {
Log.d(TAG, "Restoring newMessage $chatText for $aor::$peerUri")
unsentMessage = chatText
//newMessage.requestFocus()
BaresipService.chatTexts.remove("$aor::$peerUri")
}
_chatMessages.value = uaPeerMessages(aor, peerUri)
}
override fun onPause() {
super.onPause()
if (unsentMessage != "") {
Log.d(TAG, "Saving newMessage $unsentMessage for $aor::$peerUri")
BaresipService.chatTexts["$aor::$peerUri"] = unsentMessage
}
MainActivity.activityAor = aor
}
override fun onDestroy() { override fun onDestroy() {
if (Build.VERSION.SDK_INT >= 33) { if (Build.VERSION.SDK_INT >= 33) {
if (backInvokedCallback != null) if (backInvokedCallback != null)
@@ -44,11 +44,13 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -82,6 +84,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.core.content.ContextCompat.getString import androidx.core.content.ContextCompat.getString
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
object CustomElements { object CustomElements {
@@ -609,4 +614,20 @@ object CustomElements {
} }
} }
@Composable
fun EventListener(onEvent : (event: Lifecycle.Event) -> Unit) {
val eventHandler = rememberUpdatedState(newValue = onEvent)
val lifecycleOwner = rememberUpdatedState(newValue = LocalLifecycleOwner.current)
DisposableEffect(lifecycleOwner.value ){
val lifecycle = lifecycleOwner.value.lifecycle
val observer = LifecycleEventObserver{source, event ->
eventHandler.value(event)
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}
} }