From cbbd7e005eabdd2fe63d7dfcebe12463d492eb45 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Sun, 26 Apr 2026 08:57:33 +0300 Subject: [PATCH] Moved Contacts Mode selection from Settings screen to Contacts screen menu --- .../com/tutpro/baresip/BaresipService.kt | 2 +- .../com/tutpro/baresip/ContactsScreen.kt | 175 ++++++++++++++++++ .../com/tutpro/baresip/SettingsScreen.kt | 171 +---------------- .../com/tutpro/baresip/SettingsViewModel.kt | 3 - 4 files changed, 177 insertions(+), 174 deletions(-) diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 7b28b296..ba17f211 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -2241,7 +2241,7 @@ class BaresipService: Service() { var callHistory = ArrayList() var blocked = ArrayList() - var contactsMode = "baresip" + var contactsMode by mutableStateOf("baresip") var addressFamily = "" var dnsServers = listOf() // of those accounts that have auth username without auth password diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt index 52fa8fb3..c808e4a9 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt @@ -1,8 +1,14 @@ package com.tutpro.baresip +import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.provider.ContactsContract +import android.util.Log +import androidx.activity.compose.LocalActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -30,6 +36,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.outlined.Clear import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -47,6 +54,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale +import androidx.core.content.ContextCompat import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -92,8 +101,114 @@ private fun ContactsScreen( ) { val ctx = LocalContext.current + val activity = LocalActivity.current!! var searchContactName by remember { mutableStateOf("") } + val consentRequest = stringResource(R.string.consent_request) + val contactsConsent = stringResource(R.string.contacts_consent) + val deny = stringResource(R.string.deny) + val accept = stringResource(R.string.accept) + val notice = stringResource(R.string.notice) + val noAndroidContacts = stringResource(R.string.no_android_contacts) + val ok = stringResource(R.string.ok) + + var expanded by remember { mutableStateOf(false) } + val both = stringResource(R.string.both) + val contactNames = remember(BaresipService.contactsMode) { + val names = mutableListOf("baresip", "Android", both) + val values = listOf("baresip", "android", "both") + val index = values.indexOf(BaresipService.contactsMode) + if (index != -1) { + val name = names.removeAt(index) + names.add(0, name) + } + names + } + val contactValues = remember(BaresipService.contactsMode) { + val values = mutableListOf("baresip", "android", "both") + val index = values.indexOf(BaresipService.contactsMode) + if (index != -1) { + val value = values.removeAt(index) + values.add(0, value) + } + values + } + + val showDialog = remember { mutableStateOf(false) } + val showNoticeDialog = remember { mutableStateOf(false) } + val title = remember { mutableStateOf("") } + val message = remember { mutableStateOf("") } + val firstButtonText = remember { mutableStateOf("") } + val onFirstClicked = remember { mutableStateOf({}) } + val lastButtonText = remember { mutableStateOf("") } + val onLastClicked = remember { mutableStateOf({}) } + var pendingMode by remember { mutableStateOf("") } + + AlertDialog( + showDialog = showDialog, + title = title.value, + message = message.value, + firstButtonText = firstButtonText.value, + onFirstClicked = onFirstClicked.value, + lastButtonText = lastButtonText.value, + onLastClicked = onLastClicked.value, + ) + + fun setContactsMode(mode: String) { + if (Config.variable("contacts_mode").lowercase() != mode) { + Config.replaceVariable("contacts_mode", mode) + BaresipService.contactsMode = mode + val baresipService = Intent(ctx, BaresipService::class.java) + when (mode) { + "baresip" -> { + BaresipService.androidContacts.value = listOf() + Contact.restoreBaresipContacts() + baresipService.action = "Stop Content Observer" + } + "android" -> { + BaresipService.baresipContacts.value = mutableListOf() + Contact.loadAndroidContacts(ctx) + baresipService.action = "Start Content Observer" + } + "both" -> { + Contact.restoreBaresipContacts() + Contact.loadAndroidContacts(ctx) + baresipService.action = "Start Content Observer" + } + } + Contact.contactsUpdate() + Config.save() + ContextCompat.startForegroundService(ctx, baresipService) + } + } + + val requestPermissionsLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + if (permissions[Manifest.permission.READ_CONTACTS] == true && + permissions[Manifest.permission.WRITE_CONTACTS] == true) { + if (pendingMode.isNotEmpty()) { + setContactsMode(pendingMode) + } + } + pendingMode = "" + } + + AlertDialog( + showDialog = showNoticeDialog, + title = notice, + message = noAndroidContacts, + lastButtonText = ok, + onLastClicked = { + requestPermissionsLauncher.launch( + arrayOf( + Manifest.permission.READ_CONTACTS, + Manifest.permission.WRITE_CONTACTS + ) + ) + } + ) + Scaffold( modifier = Modifier.fillMaxSize().imePadding(), containerColor = MaterialTheme.colorScheme.background, @@ -117,6 +232,7 @@ private fun ContactsScreen( containerColor = MaterialTheme.colorScheme.primary, navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, titleContentColor = MaterialTheme.colorScheme.onPrimary, + actionIconContentColor = MaterialTheme.colorScheme.onPrimary ), navigationIcon = { IconButton( @@ -128,6 +244,65 @@ private fun ContactsScreen( ) } }, + actions = { + IconButton(onClick = { expanded = !expanded }) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = "Menu" + ) + } + CustomElements.DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + items = contactNames, + onItemClick = { name -> + expanded = false + val mode = contactValues[contactNames.indexOf(name)] + val contactsPermissions = arrayOf( + Manifest.permission.READ_CONTACTS, + Manifest.permission.WRITE_CONTACTS + ) + if (mode != "baresip" && !Utils.checkPermissions(ctx, contactsPermissions)) { + title.value = consentRequest + message.value = contactsConsent + firstButtonText.value = deny + onFirstClicked.value = { } + lastButtonText.value = accept + onLastClicked.value = { + showDialog.value = false + if (ContextCompat.checkSelfPermission( + ctx, + Manifest.permission.READ_CONTACTS + ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + ctx, + Manifest.permission.WRITE_CONTACTS + ) == PackageManager.PERMISSION_GRANTED + ) { + Log.d(TAG, "Contacts permissions already granted") + setContactsMode(mode) + } else { + if (shouldShowRequestPermissionRationale( + activity, Manifest.permission.READ_CONTACTS + ) || + shouldShowRequestPermissionRationale( + activity, Manifest.permission.WRITE_CONTACTS + ) + ) { + pendingMode = mode + showNoticeDialog.value = true + } else { + pendingMode = mode + requestPermissionsLauncher.launch(contactsPermissions) + } + } + } + showDialog.value = true + } else { + setContactsMode(mode) + } + } + ) + }, windowInsets = WindowInsets(0, 0, 0, 0), ) } diff --git a/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt index 62d7476d..dae9a79e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt @@ -811,148 +811,7 @@ private fun SettingsContent( } } - @Composable - fun Contacts(activity: Activity) { - val contactsTitle = stringResource(R.string.contacts) - val contactsHelp = stringResource(R.string.contacts_help) - val consentRequestTitle = stringResource(R.string.consent_request) - val contactsConsentMessage = stringResource(R.string.contacts_consent) - val denyText = stringResource(R.string.deny) - val acceptText = stringResource(R.string.accept) - val both = stringResource(R.string.both) - val noAndroidContactsMessage = stringResource(R.string.no_android_contacts) - val showAlertDialog = remember { mutableStateOf(false) } - Row( - Modifier - .fillMaxWidth() - .padding(top = 12.dp) - .padding(end = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start - ) { - val ctx = LocalContext.current - Text(text = contactsTitle, - modifier = Modifier - .weight(1f) - .clickable { - alertTitle.value = contactsTitle - alertMessage.value = contactsHelp - showAlert.value = true - }, - fontSize = 18.sp - ) - val isDropDownExpanded = remember { - mutableStateOf(false) - } - val contactNames = listOf( - "baresip", - "Android", - both - ) - val contactsMode by viewModel.contactsMode.collectAsState() - val contactValues = listOf("baresip", "android", "both") - val itemPosition = remember { - mutableIntStateOf(contactValues.indexOf(contactsMode)) - } - val requestPermissionsLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.RequestMultiplePermissions() - ) {} - val contactsPermissions = arrayOf(Manifest.permission.READ_CONTACTS, - Manifest.permission.WRITE_CONTACTS) - Box { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { - isDropDownExpanded.value = true - } - ) { - Text(text = contactNames[itemPosition.intValue]) - Icon( - imageVector = Icons.Filled.ArrowDropDown, - contentDescription = null, - modifier = Modifier.size(36.dp) - ) - } - DropdownMenu( - expanded = isDropDownExpanded.value, - onDismissRequest = { - isDropDownExpanded.value = false - }) { - contactNames.forEachIndexed { index, name -> - DropdownMenuItem( - text = { Text(text = name) }, - onClick = { - isDropDownExpanded.value = false - val mode = contactValues[index] - if (mode != "baresip" && !Utils.checkPermissions(ctx, contactsPermissions)) { - dialogTitle.value = consentRequestTitle - dialogMessage.value = contactsConsentMessage - firstButtonText.value = denyText - onFirstClicked.value = { - itemPosition.intValue = contactValues.indexOf(contactsMode) - } - lastButtonText.value = acceptText - onLastClicked.value = { - showDialog.value = false - viewModel.contactsMode.value = mode - if (ContextCompat.checkSelfPermission( - ctx, - Manifest.permission.READ_CONTACTS - ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( - ctx, - Manifest.permission.WRITE_CONTACTS - ) == PackageManager.PERMISSION_GRANTED - ) { - Log.d(TAG, "Contacts permissions already granted") - } - else { - if (shouldShowRequestPermissionRationale( - activity, Manifest.permission.READ_CONTACTS - ) || - shouldShowRequestPermissionRationale( - activity, Manifest.permission.WRITE_CONTACTS - ) - ) - showAlertDialog.value = true - else - requestPermissionsLauncher.launch( - arrayOf( - Manifest.permission.READ_CONTACTS, - Manifest.permission.WRITE_CONTACTS - ) - ) - } - } - showDialog.value = true - } - else { - itemPosition.intValue = index - viewModel.contactsMode.value = contactValues[index] - } - }) - if (index < 2) - HorizontalDivider(thickness = 1.dp) - } - } - } - if (showAlertDialog.value) - AlertDialog( - showDialog = showAlertDialog, - title = noticeTitleText, - message = noAndroidContactsMessage, - firstButtonText = "", - onFirstClicked = {}, - lastButtonText = okButtonText, - onLastClicked = { requestPermissionsLauncher.launch( - arrayOf( - Manifest.permission.READ_CONTACTS, - Manifest.permission.WRITE_CONTACTS - ) - )}, - ) - } - } + @Composable fun Ringtone() { @@ -1349,7 +1208,6 @@ private fun SettingsContent( UserAgent() UniqueContactUri() AudioSettings(navController) - Contacts(activity) Ringtone() BatteryOptimizations() DarkTheme() @@ -1476,33 +1334,6 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean { Preferences(ctx).ringtoneUri = ringtoneUri BaresipService.rt = RingtoneManager.getRingtone(ctx, ringtoneUri.toUri()) - val contactsMode = viewModel.contactsMode.value - if (Config.variable("contacts_mode").lowercase() != contactsMode) { - Config.replaceVariable("contacts_mode", contactsMode) - BaresipService.contactsMode = contactsMode - val baresipService = Intent(ctx, BaresipService::class.java) - when (contactsMode) { - "baresip" -> { - BaresipService.androidContacts.value = listOf() - Contact.restoreBaresipContacts() - baresipService.action = "Stop Content Observer" - } - "android" -> { - BaresipService.baresipContacts.value = mutableListOf() - Contact.loadAndroidContacts(ctx) - baresipService.action = "Start Content Observer" - } - "both" -> { - Contact.restoreBaresipContacts() - Contact.loadAndroidContacts(ctx) - baresipService.action = "Start Content Observer" - } - } - Contact.contactsUpdate() - ContextCompat.startForegroundService(ctx, baresipService) - save = true - } - val darkTheme = viewModel.darkTheme.value val newDisplayTheme = if (darkTheme) AppCompatDelegate.MODE_NIGHT_YES diff --git a/app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt b/app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt index a3aaa600..2332d28c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt @@ -25,7 +25,6 @@ class SettingsViewModel: ViewModel() { val caFile = MutableStateFlow(false) val userAgent = MutableStateFlow("") val uniqueContactUri = MutableStateFlow(true) - val contactsMode = MutableStateFlow("") val ringtoneUri = MutableStateFlow("") val batteryOptimizations = MutableStateFlow(false) val darkTheme = MutableStateFlow(false) @@ -65,8 +64,6 @@ class SettingsViewModel: ViewModel() { uniqueContactUri.value = Config.variable("sip_cuser_random") == "yes" - contactsMode.value = Config.variable("contacts_mode").lowercase() - ringtoneUri.value = if (Preferences(ctx).ringtoneUri == "") RingtoneManager.getActualDefaultRingtoneUri(ctx, RingtoneManager.TYPE_RINGTONE).toString() else