Moved Contacts Mode selection from Settings screen to Contacts screen menu

This commit is contained in:
Juha Heinanen
2026-04-26 08:57:33 +03:00
parent ad3999ef7d
commit cbbd7e005e
4 changed files with 177 additions and 174 deletions

View File

@ -2241,7 +2241,7 @@ class BaresipService: Service() {
var callHistory = ArrayList<CallHistoryNew>()
var blocked = ArrayList<Blocked>()
var contactsMode = "baresip"
var contactsMode by mutableStateOf("baresip")
var addressFamily = ""
var dnsServers = listOf<InetAddress>()
// <aor, password> of those accounts that have auth username without auth password

View File

@ -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),
)
}

View File

@ -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

View File

@ -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