diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt index 283bf3b0..944c7a37 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt @@ -61,25 +61,31 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle 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.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.sp +import androidx.lifecycle.Lifecycle import androidx.lifecycle.Observer import com.tutpro.baresip.CustomElements.AlertDialog +import com.tutpro.baresip.CustomElements.EventListener import com.tutpro.baresip.CustomElements.LabelText import com.tutpro.baresip.CustomElements.verticalScrollbar import kotlinx.coroutines.launch @@ -99,7 +105,6 @@ class ChatActivity : ComponentActivity() { private var chatMessages : List by _chatMessages private var focus = false private var lastCall: Long = 0 - private var unsentMessage = "" private var keyboardController: SoftwareKeyboardController? = null private var backInvokedCallback: OnBackInvokedCallback? = null @@ -267,7 +272,9 @@ class ChatActivity : ComponentActivity() { aor.split(":")[1] Text( 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, fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center @@ -406,8 +413,10 @@ class ChatActivity : ComponentActivity() { modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .padding(start = if (message.direction == MESSAGE_DOWN) 0.dp else 24.dp, - end = if (message.direction == MESSAGE_DOWN) 24.dp else 0.dp) + .padding( + start = if (message.direction == MESSAGE_DOWN) 0.dp else 24.dp, + end = if (message.direction == MESSAGE_DOWN) 24.dp else 0.dp + ) ) { Column { Row { @@ -449,6 +458,36 @@ class ChatActivity : ComponentActivity() { @Composable 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 dialogMessage = remember { mutableStateOf("") } @@ -459,7 +498,6 @@ class ChatActivity : ComponentActivity() { positiveButtonText = stringResource(R.string.ok), ) - var newMessage by remember { mutableStateOf("") } Row(modifier = Modifier .fillMaxWidth() .navigationBarsPadding() @@ -468,16 +506,27 @@ class ChatActivity : ComponentActivity() { ) { val keyboardController = LocalSoftwareKeyboardController.current OutlinedTextField( - value = newMessage, + value = newMessage.value.text, placeholder = { Text(stringResource(R.string.new_message)) }, - onValueChange = { newMessage = it }, + onValueChange = { + newMessage.value = newMessage.value.copy(text = it) + }, modifier = Modifier .weight(1f) .padding(end = 8.dp) .verticalScroll(rememberScrollState()) + .onGloballyPositioned { + if (!textFieldLoaded) { + textFieldLoaded = true + //val textLength = newMessage.value.text.length + //newMessage.value = newMessage.value.copy( + // selection = TextRange(textLength) + //) + } + } .combinedClickable( onClick = { - val msgText = newMessage + val msgText = newMessage.value.text if (msgText.isNotEmpty()) { keyboardController?.hide() val time = System.currentTimeMillis() @@ -498,7 +547,8 @@ class ChatActivity : ComponentActivity() { if (ua.account.telProvider == "") { dialogMessage.value = String.format( getString(R.string.no_telephony_provider), - Utils.plainAor(aor)) + Utils.plainAor(aor) + ) showDialog.value = true } else { msgUri = Utils.telToSip(peerUri, ua.account) @@ -513,12 +563,14 @@ class ChatActivity : ComponentActivity() { 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.responseReason = getString(R.string.message_failed) } else { - newMessage = "" - unsentMessage = "" + newMessage.value = TextFieldValue("") keyboardController?.hide() BaresipService.chatTexts.remove("$aor::$peerUri") } @@ -531,7 +583,7 @@ class ChatActivity : ComponentActivity() { val clipData = clipboardManager.primaryClip if (clipData != null && clipData.itemCount > 0) { val pastedText = clipData.getItemAt(0).text.toString() - newMessage = pastedText + newMessage.value = TextFieldValue(pastedText) } else { Toast.makeText(ctx, "Nothing to paste", Toast.LENGTH_SHORT).show() } @@ -539,11 +591,11 @@ class ChatActivity : ComponentActivity() { ), singleLine = false, trailingIcon = { - if (newMessage.isNotEmpty()) { + if (newMessage.value.text.isNotEmpty()) { Icon( Icons.Outlined.Clear, contentDescription = "Clear", - modifier = Modifier.clickable { newMessage = "" } + modifier = Modifier.clickable { newMessage.value = TextFieldValue("") } ) } }, label = { LabelText(stringResource(R.string.new_message)) }, @@ -554,26 +606,21 @@ class ChatActivity : ComponentActivity() { autoCorrectEnabled = true ) ) + LaunchedEffect(Unit) { + } Image( painter = painterResource(id = R.drawable.send), contentDescription = "Send", contentScale = ContentScale.Crop, - modifier = Modifier.size(36.dp) + modifier = Modifier + .size(36.dp) .clickable { - val msgText = newMessage + val msgText = newMessage.value.text if (msgText.isNotEmpty()) { keyboardController?.hide() val time = System.currentTimeMillis() - val msg = Message( - aor, - peerUri, - msgText, - time, - MESSAGE_UP_WAIT, - 0, - "", - true - ) + val msg = + Message(aor, peerUri, msgText, time, MESSAGE_UP_WAIT, 0, "", true) msg.add() var msgUri = "" _chatMessages.value += msg @@ -581,7 +628,8 @@ class ChatActivity : ComponentActivity() { if (ua.account.telProvider == "") { dialogMessage.value = String.format( getString(R.string.no_telephony_provider), - Utils.plainAor(aor)) + Utils.plainAor(aor) + ) showDialog.value = true } else { msgUri = Utils.telToSip(peerUri, ua.account) @@ -589,19 +637,16 @@ class ChatActivity : ComponentActivity() { else msgUri = peerUri if (msgUri != "") - if (Api.message_send( - ua.uap, - msgUri, - msgText, - time.toString() - ) != 0 + if (Api.message_send(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.responseReason = getString(R.string.message_failed) } else { - newMessage = "" - unsentMessage = "" + newMessage.value = TextFieldValue("") keyboardController?.hide() 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() { if (Build.VERSION.SDK_INT >= 33) { if (backInvokedCallback != null) diff --git a/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt b/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt index 7fe847d7..d92d1ef3 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt @@ -44,11 +44,13 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.window.DialogProperties import androidx.core.content.ContextCompat.getString +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner 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) + } + } + } + }