diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 86cd169c..0c413901 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -94,6 +94,11 @@ + + + + + { + val uap = intent!!.getLongExtra("uap", 0L) + val ua = UserAgent.ofUap(uap) + if (ua != null) { + val peerUri = intent.getStringExtra("peer") + if (peerUri != null) + Message.updateMessagesFromPearRead(ua.account.aor, peerUri) + else { + for (m in messages) + if (m.aor == ua.account.aor) + m.new = false + Message.save() + } + ua.account.unreadMessages = Message.unreadMessages(ua.account.aor) + } + nm.cancel(MESSAGE_NOTIFICATION_ID) + } + "Message Inline Reply" -> { val remoteInputResults = RemoteInput.getResultsFromIntent(intent!!) if (remoteInputResults != null) { @@ -777,6 +795,14 @@ class BaresipService: Service() { addMobileUserAgent() } + "Clear Missed" -> { + val uap = intent!!.getLongExtra("uap", 0L) + val ua = UserAgent.ofUap(uap) + if (ua != null) + ua.account.missedCalls = false + nm.cancel(CALL_MISSED_NOTIFICATION_ID) + } + "Start Call" -> { val uap = intent!!.getLongExtra("uap", 0L) val uri = intent.getStringExtra("uri")!! @@ -1560,10 +1586,16 @@ class BaresipService: Service() { .setGroupConversation(false) .addMessage(text, timeStamp, sender) + val clearIntent = Intent(this, BaresipService::class.java) + clearIntent.action = "Clear Unread" + clearIntent.putExtra("uap", uap).putExtra("peer", peerUri) + val dpi = PendingIntent.getService(this, MESSAGE_NOTIFICATION_ID, clearIntent, piFlags) + val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification_message) .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) .setContentIntent(pi) + .setDeleteIntent(dpi) .setSound(Settings.System.DEFAULT_NOTIFICATION_URI) .setAutoCancel(true) .setStyle(messagingStyle) @@ -1579,9 +1611,7 @@ class BaresipService: Service() { } nb.setNumber(unreadMessages + 1) - val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY) - .setLabel(getString(R.string.reply)) - .build() + val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(getString(R.string.reply)).build() val directReplyIntent = Intent(this, BaresipService::class.java) directReplyIntent.action = "Message Inline Reply" directReplyIntent.putExtra("uap", uap).putExtra("peer", peerUri).putExtra("time", timeStamp) @@ -1602,10 +1632,8 @@ class BaresipService: Service() { val saveIntent = Intent(this, BaresipService::class.java) saveIntent.action = "Message Save" - saveIntent.putExtra("uap", uap) - .putExtra("time", timeStamp.toString()) - val savePendingIntent = PendingIntent - .getService(this, SAVE_REQ_CODE, saveIntent, piFlags) + saveIntent.putExtra("uap", uap).putExtra("time", timeStamp.toString()) + val savePendingIntent = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent, piFlags) val saveAction = NotificationCompat.Action.Builder( R.drawable.ic_notification_save, getString(R.string.save), @@ -1614,10 +1642,8 @@ class BaresipService: Service() { val deleteIntent = Intent(this, BaresipService::class.java) deleteIntent.action = "Message Delete" - deleteIntent.putExtra("uap", uap) - .putExtra("time", timeStamp.toString()) - val deletePendingIntent = PendingIntent - .getService(this, DELETE_REQ_CODE, deleteIntent, piFlags) + deleteIntent.putExtra("uap", uap).putExtra("time", timeStamp.toString()) + val deletePendingIntent = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent, piFlags) val deleteAction = NotificationCompat.Action.Builder( R.drawable.ic_notification_delete, getString(R.string.delete), @@ -2250,7 +2276,8 @@ class BaresipService: Service() { history.add() if (call.dir == "in" && call.startTime == null && !call.rejected) { call.ua.account.missedCalls = true - showMissedCallNotification(uap, call.peerUri, call.ua.account.aor) + if (!call.ua.account.isMobile) + showMissedCallNotification(uap, call.peerUri, call.ua.account.aor) } } synchronized(calls) { @@ -3080,10 +3107,17 @@ class BaresipService: Service() { intent, piFlags ) + + val deleteIntent = Intent(this, BaresipService::class.java) + deleteIntent.action = "Clear Missed" + deleteIntent.putExtra("uap", uap) + val dpi = PendingIntent.getService(this, CALL_MISSED_NOTIFICATION_ID, deleteIntent, piFlags) + val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID) nb.setSmallIcon(R.drawable.ic_notification_call_missed) .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) .setContentIntent(pi) + .setDeleteIntent(dpi) .setCategory(NotificationCompat.CATEGORY_MISSED_CALL) .setAutoCancel(true) diff --git a/app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt index e881cb47..3a9c161d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt @@ -114,8 +114,13 @@ private fun CallsScreen(navController: NavController, viewModel: ViewModel, aor: } } + val ctx = LocalContext.current + BackHandler(enabled = true) { - ua.account.missedCalls = false + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Missed" + serviceIntent.putExtra("uap", ua.uap) + ctx.startService(serviceIntent) navController.navigateUp() } @@ -182,9 +187,13 @@ private fun TopAppBar(navController: NavController, ua: UserAgent, callHistory: ), windowInsets = WindowInsets(0, 0, 0, 0), navigationIcon = { + val ctx = LocalContext.current IconButton( onClick = { - account.missedCalls = false + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Missed" + serviceIntent.putExtra("uap", ua.uap) + ctx.startService(serviceIntent) navController.navigateUp() } ) { @@ -495,7 +504,7 @@ private fun Calls( count++ } if (count > 3) - Text("...", color = MaterialTheme.colorScheme.onBackground) + Text(ctx.getString(R.string.dots), color = MaterialTheme.colorScheme.onBackground) Text( text = Utils.friendlyUri(ctx, peerUri, ua.account), modifier = Modifier.padding(start = 8.dp), diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt index bb3c9cdf..fdab48cc 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt @@ -1,8 +1,8 @@ package com.tutpro.baresip -import android.content.Context import android.content.Intent import android.text.format.DateUtils.isToday +import android.util.Log import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi @@ -102,7 +102,6 @@ fun NavGraphBuilder.chatScreenRoute(navController: NavController, viewModel: Vie val aor = backStackEntry.arguments?.getString("aor")!! val peerUri = backStackEntry.arguments?.getString("peer")!! ChatScreen( - ctx = LocalContext.current, navController = navController, viewModel = viewModel, account = Account.ofAor(aor)!!, @@ -113,13 +112,13 @@ fun NavGraphBuilder.chatScreenRoute(navController: NavController, viewModel: Vie @Composable private fun ChatScreen( - ctx: Context, navController: NavController, viewModel: ViewModel, account: Account, peerUri: String ) { val lifecycleOwner = LocalLifecycleOwner.current + val ctx = LocalContext.current val aor = account.aor @@ -164,6 +163,11 @@ private fun ChatScreen( } BackHandler(enabled = true) { + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Unread" + serviceIntent.putExtra("uap", account.accp) + serviceIntent.putExtra("peer", peerUri) + ctx.startService(serviceIntent) backAction(navController, account, peerUri) } @@ -173,12 +177,11 @@ private fun ChatScreen( topBar = { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { Spacer(Modifier.statusBarsPadding()) - TopAppBar(ctx, navController, viewModel, account, peerUri) + TopAppBar(navController, viewModel, account, peerUri) } }, bottomBar = { NewMessage( - ctx = ctx, viewModel, account = account, peerUri = peerUri, @@ -188,7 +191,6 @@ private fun ChatScreen( content = { contentPadding -> if (areMessagesLoaded) ChatContent( - ctx, contentPadding, account, peerUri, chatMessages, @@ -201,12 +203,12 @@ private fun ChatScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TopAppBar( - ctx: Context, navController: NavController, viewModel: ViewModel, account: Account, peerUri: String ) { + val ctx = LocalContext.current val aor = account.aor TopAppBar( title = { @@ -223,7 +225,17 @@ private fun TopAppBar( actionIconContentColor = MaterialTheme.colorScheme.onPrimary ), navigationIcon = { - IconButton(onClick = { backAction(navController, account, peerUri) }) { + val ctx = LocalContext.current + IconButton( + onClick = { + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Unread" + serviceIntent.putExtra("uap", account.accp) + serviceIntent.putExtra("peer", peerUri) + ctx.startService(serviceIntent) + backAction(navController, account, peerUri) + } + ) { Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } }, @@ -282,7 +294,6 @@ private fun TopAppBar( @Composable private fun ChatContent( - ctx: Context, contentPadding: PaddingValues, account: Account, peerUri: String, @@ -293,14 +304,15 @@ private fun ChatContent( modifier = Modifier.fillMaxWidth().padding(contentPadding), verticalArrangement = Arrangement.Bottom ) { - Account(ctx, account) + Account(account) Spacer(modifier = Modifier.weight(1f)) - Messages(ctx, account, peerUri, messages, onMessageDeleted) + Messages(account, peerUri, messages, onMessageDeleted) } } @Composable -private fun Account(ctx: Context, account: Account) { +private fun Account(account: Account) { + val ctx = LocalContext.current Text( text = ctx.getString(R.string.account) + " " + account.text(), modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp), @@ -312,12 +324,12 @@ private fun Account(ctx: Context, account: Account) { @Composable private fun Messages( - ctx: Context, account: Account, peerUri: String, messages: List, onMessageDeleted: () -> Unit ) { + val ctx = LocalContext.current val peerName = Utils.friendlyUri(ctx, peerUri, account) val showDialog = remember { mutableStateOf(false) } @@ -444,13 +456,12 @@ private fun Messages( @OptIn(ExperimentalFoundationApi::class) @Composable private fun NewMessage( - ctx: Context, viewModel: ViewModel, account: Account, peerUri: String, addMessage: (message: Message) -> Unit ) { - + val ctx = LocalContext.current val aor = account.aor val ua = UserAgent.ofAor(aor)!! diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt index e3be9282..d7145fa6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt @@ -1,6 +1,7 @@ package com.tutpro.baresip -import android.content.Context +import android.content.Intent +import androidx.activity.compose.BackHandler import android.text.format.DateUtils.isToday import androidx.compose.animation.animateContentSize import androidx.compose.foundation.BorderStroke @@ -13,11 +14,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding @@ -129,8 +128,15 @@ private fun ChatsScreen(navController: NavController, aor: String) { onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } + BackHandler(enabled = true) { + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Unread" + serviceIntent.putExtra("uap", account.accp) + ctx.startService(serviceIntent) + navController.navigateUp() + } + Scaffold( - modifier = Modifier.fillMaxSize().imePadding(), containerColor = MaterialTheme.colorScheme.background, topBar = { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { @@ -138,10 +144,10 @@ private fun ChatsScreen(navController: NavController, aor: String) { TopAppBar(navController, account, uaMessages) } }, - bottomBar = { NewChatPeer(ctx, navController, account) }, + bottomBar = { NewChatPeer(navController, account) }, content = { contentPadding -> if (areMessagesLoaded) - ChatsContent(LocalContext.current, navController, contentPadding, account, uaMessages) + ChatsContent(navController, contentPadding, account, uaMessages) }, ) } @@ -178,7 +184,16 @@ private fun TopAppBar( actionIconContentColor = MaterialTheme.colorScheme.onPrimary ), navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + val ctx = LocalContext.current + IconButton( + onClick = { + val serviceIntent = Intent(ctx, BaresipService::class.java) + serviceIntent.action = "Clear Unread" + serviceIntent.putExtra("uap", account.accp) + ctx.startService(serviceIntent) + navController.navigateUp() + } + ) { Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) } }, @@ -214,7 +229,6 @@ private fun TopAppBar( @Composable private fun ChatsContent( - ctx: Context, navController: NavController, contentPadding: PaddingValues, account: Account, @@ -225,7 +239,7 @@ private fun ChatsContent( verticalArrangement = Arrangement.Top ) { Account(account) - Chats(ctx, navController, account, uaMessages) + Chats(navController, account, uaMessages) } } @@ -242,11 +256,11 @@ private fun Account(account: Account) { @Composable private fun Chats( - ctx: Context, navController: NavController, account: Account, uaMessages: MutableState> ) { + val ctx = LocalContext.current val aor = account.aor val showDialog = remember { mutableStateOf(false) } @@ -409,13 +423,14 @@ private fun Chats( } @Composable -private fun NewChatPeer(ctx: Context, navController: NavController, account: Account) { +private fun NewChatPeer(navController: NavController, account: Account) { + val ctx = LocalContext.current val alertTitle = remember { mutableStateOf("") } val alertMessage = remember { mutableStateOf("") } val showAlert = remember { mutableStateOf(false) } - fun makeChat(ctx: Context, navController: NavController, account: Account, chatPeer: String) { + fun makeChat(navController: NavController, account: Account, chatPeer: String) { val peerUri = if (Utils.isTelNumber(chatPeer)) "tel:$chatPeer" else @@ -587,15 +602,15 @@ private fun NewChatPeer(ctx: Context, navController: NavController, account: Acc String.format(ctx.getString(R.string.contact_no_sip_or_tel_uri), peerText) showAlert.value = true } else { - makeChat(ctx, navController, account, peerText) + makeChat(navController, account, peerText) } } else if (uris.size == 1) - makeChat(ctx, navController, account, uris[0].uri) + makeChat(navController, account, uris[0].uri) else { items.value = uris.map { it.label.ifEmpty { it.uri.substringAfter(":") } } itemAction.value = { index -> - makeChat(ctx, navController, account, uris[index].uri) + makeChat(navController, account, uris[index].uri) } showDialog.value = true } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 03c57169..c07f79ff 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -20,6 +20,7 @@ import android.content.IntentFilter import android.media.AudioManager import android.os.Build import android.os.Bundle +import android.provider.CallLog import android.view.KeyEvent import android.view.WindowManager import androidx.activity.ComponentActivity @@ -299,13 +300,21 @@ class MainActivity : ComponentActivity() { override fun onStart() { super.onStart() - Log.i(TAG, "Main onStart") + Log.i(TAG, "Main onStart action/type/data: ${intent.action}/${intent.type}/${intent.data}") val action = intent.getStringExtra("action") if (action != null) { // MainActivity was not visible when call, message, or transfer request came in intent.removeExtra("action") handleIntent(applicationContext, viewModel, intent, action) } + else if (intent.action == ACTION_VIEW && (intent.type == CallLog.Calls.CONTENT_TYPE || + intent.data?.toString()?.contains("calls") == true)) { + Log.d(TAG, "onStart: Handling Call Log intent") + val ua = BaresipService.uas.value.find { it.account.isMobile } + ?: BaresipService.uas.value.firstOrNull() + if (ua != null) + viewModel.navigateToCalls(ua.account.aor) + } else if (intent.action == Intent.ACTION_MAIN && !atStartup) { val activeNotifications = nm.activeNotifications if (activeNotifications.any { it.id == CALL_MISSED_NOTIFICATION_ID }) { @@ -361,16 +370,25 @@ class MainActivity : ComponentActivity() { this.setShowWhenLocked(true) this.setTurnScreenOn(true) - Log.d(TAG, "onNewIntent with action/data '${intent.action}/${intent.data}'") + Log.d(TAG, "onNewIntent action/type/data: ${intent.action}/${intent.type}/${intent.data}") - when (intent.action) { - ACTION_DIAL, ACTION_CALL, ACTION_VIEW -> + when { + intent.action == ACTION_VIEW && (intent.type == CallLog.Calls.CONTENT_TYPE || + intent.data?.toString()?.contains("calls") == true) -> { + Log.d(TAG, "onNewIntent: Handling Call Log intent") + val ua = BaresipService.uas.value.find { it.account.isMobile } + ?: BaresipService.uas.value.firstOrNull() + if (ua != null) + viewModel.navigateToCalls(ua.account.aor) + } + intent.action in listOf(ACTION_DIAL, ACTION_CALL, ACTION_VIEW) -> { callAction( applicationContext, viewModel, intent.data, if (intent.action == ACTION_CALL) "call" else "dial" ) + } else -> { val action = intent.getStringExtra("action") if (action != null) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt b/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt index a8d59850..a59ae4fd 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.vector.ImageVector import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -79,7 +80,7 @@ class ViewModel: ViewModel() { private val _hideKeyboard = MutableStateFlow(0) val hideKeyboard = _hideKeyboard.asStateFlow() - private val _navigationCommand = MutableSharedFlow() + private val _navigationCommand = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) val navigationCommand = _navigationCommand.asSharedFlow() private var lastRenderedAor = ""