Renamed AndroidContactScreen to ContactScreen and use it for both contact types
This commit is contained in:
@@ -46,6 +46,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -54,6 +55,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -495,19 +497,22 @@ private fun ContactUris(uris: List<String>, onUrisChange: (List<String>) -> Unit
|
|||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
|
||||||
)
|
)
|
||||||
if (uris.size > 1) {
|
if (uris.size > 1) {
|
||||||
IconButton(
|
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||||
onClick = {
|
IconButton(
|
||||||
val newList = uris.toMutableList()
|
onClick = {
|
||||||
newList.removeAt(index)
|
val newList = uris.toMutableList()
|
||||||
onUrisChange(newList.toList())
|
newList.removeAt(index)
|
||||||
},
|
onUrisChange(newList.toList())
|
||||||
modifier = Modifier.padding(start = 4.dp)
|
},
|
||||||
) {
|
modifier = Modifier.padding(start = 2.dp)
|
||||||
Icon(
|
) {
|
||||||
imageVector = Icons.Filled.Remove,
|
Icon(
|
||||||
contentDescription = "Remove",
|
imageVector = Icons.Filled.Remove,
|
||||||
tint = MaterialTheme.colorScheme.error
|
contentDescription = "Remove",
|
||||||
)
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class BaresipService: Service() {
|
|||||||
private lateinit var wifiLock: WifiManager.WifiLock
|
private lateinit var wifiLock: WifiManager.WifiLock
|
||||||
private lateinit var bluetoothReceiver: BroadcastReceiver
|
private lateinit var bluetoothReceiver: BroadcastReceiver
|
||||||
private lateinit var hotSpotReceiver: BroadcastReceiver
|
private lateinit var hotSpotReceiver: BroadcastReceiver
|
||||||
|
private lateinit var airplaneModeReceiver: BroadcastReceiver
|
||||||
private lateinit var androidContactsObserver: ContentObserver
|
private lateinit var androidContactsObserver: ContentObserver
|
||||||
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
|
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
|
||||||
private lateinit var stopState: String
|
private lateinit var stopState: String
|
||||||
@@ -120,6 +121,7 @@ class BaresipService: Service() {
|
|||||||
private var androidContactsObserverRegistered = false
|
private var androidContactsObserverRegistered = false
|
||||||
private var hotSpotReceiverRegistered = false
|
private var hotSpotReceiverRegistered = false
|
||||||
private var bluetoothReceiverRegistered = false
|
private var bluetoothReceiverRegistered = false
|
||||||
|
private var airplaneModeReceiverRegistered = false
|
||||||
private var isNotificationInCall = false
|
private var isNotificationInCall = false
|
||||||
private var isServiceClean = false
|
private var isServiceClean = false
|
||||||
private var cleanupRunnable: Runnable? = null
|
private var cleanupRunnable: Runnable? = null
|
||||||
@@ -288,6 +290,23 @@ class BaresipService: Service() {
|
|||||||
)
|
)
|
||||||
hotSpotReceiverRegistered = true
|
hotSpotReceiverRegistered = true
|
||||||
|
|
||||||
|
airplaneModeReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context, intent: Intent) {
|
||||||
|
if (intent.action == Intent.ACTION_AIRPLANE_MODE_CHANGED) {
|
||||||
|
val isAirplaneModeOn = intent.getBooleanExtra("state", false)
|
||||||
|
Log.d(TAG, "Airplane mode changed: $isAirplaneModeOn")
|
||||||
|
updateMobileStatus(isAirplaneModeOn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
this,
|
||||||
|
airplaneModeReceiver,
|
||||||
|
IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
airplaneModeReceiverRegistered = true
|
||||||
|
|
||||||
tm = getSystemService(TELECOM_SERVICE) as TelecomManager
|
tm = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
|
|
||||||
btm = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
|
btm = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
@@ -2027,6 +2046,8 @@ class BaresipService: Service() {
|
|||||||
account.regint = 0
|
account.regint = 0
|
||||||
account.telProvider = ""
|
account.telProvider = ""
|
||||||
val mobileUa = UserAgent(0L, account)
|
val mobileUa = UserAgent(0L, account)
|
||||||
|
val isAirplaneModeOn = Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
|
||||||
|
mobileUa.status = if (isAirplaneModeOn) R.drawable.circle_white else circleGreen.getValue(colorblind)
|
||||||
|
|
||||||
val updatedUas = uas.value.toMutableList()
|
val updatedUas = uas.value.toMutableList()
|
||||||
updatedUas.add(mobileUa)
|
updatedUas.add(mobileUa)
|
||||||
@@ -2042,6 +2063,19 @@ class BaresipService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateMobileStatus(isAirplaneModeOn: Boolean) {
|
||||||
|
uas.value.find { it.account.isMobile }?.let { ua ->
|
||||||
|
val status = if (isAirplaneModeOn)
|
||||||
|
R.drawable.circle_white
|
||||||
|
else
|
||||||
|
circleGreen.getValue(colorblind)
|
||||||
|
ua.updateStatus(status)
|
||||||
|
updateStatusNotification()
|
||||||
|
if (isMainVisible)
|
||||||
|
registrationUpdate.postValue(System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("FullScreenIntentPolicy")
|
@SuppressLint("FullScreenIntentPolicy")
|
||||||
fun handleIncomingCall(call: Call) {
|
fun handleIncomingCall(call: Call) {
|
||||||
val ua = call.ua
|
val ua = call.ua
|
||||||
@@ -2635,6 +2669,12 @@ class BaresipService: Service() {
|
|||||||
bluetoothReceiverRegistered = false
|
bluetoothReceiverRegistered = false
|
||||||
} catch (_: IllegalArgumentException) {}
|
} catch (_: IllegalArgumentException) {}
|
||||||
}
|
}
|
||||||
|
if (airplaneModeReceiverRegistered) {
|
||||||
|
try {
|
||||||
|
unregisterReceiver(airplaneModeReceiver)
|
||||||
|
airplaneModeReceiverRegistered = false
|
||||||
|
} catch (_: IllegalArgumentException) {}
|
||||||
|
}
|
||||||
val callps = ConnectionService.connections.keys.toList()
|
val callps = ConnectionService.connections.keys.toList()
|
||||||
for (callp in callps)
|
for (callp in callps)
|
||||||
ConnectionService.onCallClosed(callp)
|
ConnectionService.onCallClosed(callp)
|
||||||
|
|||||||
@@ -13,45 +13,38 @@ import java.util.ArrayList
|
|||||||
|
|
||||||
sealed class Contact {
|
sealed class Contact {
|
||||||
|
|
||||||
|
abstract fun name(): String
|
||||||
|
abstract fun id(): Long
|
||||||
|
abstract fun favorite(): Boolean
|
||||||
|
abstract fun colorInt(): Int
|
||||||
|
abstract fun uris(): List<String>
|
||||||
|
abstract fun email(): String
|
||||||
|
|
||||||
class BaresipContact(var name: String, val uris: ArrayList<String>, var email: String, var color: Int,
|
class BaresipContact(var name: String, val uris: ArrayList<String>, var email: String, var color: Int,
|
||||||
var id: Long, var favorite: Boolean): Contact() {
|
var id: Long, var favorite: Boolean): Contact() {
|
||||||
var avatarImage: Bitmap? = null
|
var avatarImage: Bitmap? = null
|
||||||
|
override fun name() = name
|
||||||
|
override fun id() = id
|
||||||
|
override fun favorite() = favorite
|
||||||
|
override fun colorInt() = color
|
||||||
|
override fun uris() = uris
|
||||||
|
override fun email() = email
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidContact(var name: String, var color: Int, var thumbnailUri: Uri?,
|
class AndroidContact(var name: String, val uris: ArrayList<String>, var email: String, var color: Int,
|
||||||
var id: Long, var favorite: Boolean): Contact() {
|
var thumbnailUri: Uri?, var id: Long, var favorite: Boolean): Contact() {
|
||||||
val uris = ArrayList<String>()
|
override fun name() = name
|
||||||
}
|
override fun id() = id
|
||||||
|
override fun favorite() = favorite
|
||||||
fun name(): String {
|
override fun colorInt() = color
|
||||||
return when (this) {
|
override fun uris() = uris
|
||||||
is AndroidContact -> name
|
override fun email() = email
|
||||||
is BaresipContact -> name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun id(): Long {
|
|
||||||
return when (this) {
|
|
||||||
is AndroidContact -> id
|
|
||||||
is BaresipContact -> id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun favorite(): Boolean {
|
|
||||||
return when (this) {
|
|
||||||
is AndroidContact -> favorite
|
|
||||||
is BaresipContact -> favorite
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun color(): String {
|
fun color(): String {
|
||||||
val intColor = when (this) {
|
|
||||||
is AndroidContact -> color
|
|
||||||
is BaresipContact -> color
|
|
||||||
}
|
|
||||||
// Mask with 0xFFFFFF to ensure we get the standard 6-character hex code (RRGGBB)
|
// Mask with 0xFFFFFF to ensure we get the standard 6-character hex code (RRGGBB)
|
||||||
// ignoring the alpha channel for compatibility with simple string parsers.
|
// ignoring the alpha channel for compatibility with simple string parsers.
|
||||||
return String.format("#%06X", 0xFFFFFF and intColor)
|
return String.format("#%06X", 0xFFFFFF and colorInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copy(): Contact {
|
fun copy(): Contact {
|
||||||
@@ -59,14 +52,10 @@ sealed class Contact {
|
|||||||
is BaresipContact ->
|
is BaresipContact ->
|
||||||
BaresipContact(name, ArrayList(uris), email, color, id, favorite)
|
BaresipContact(name, ArrayList(uris), email, color, id, favorite)
|
||||||
is AndroidContact ->
|
is AndroidContact ->
|
||||||
AndroidContact(name, color, thumbnailUri, id, favorite)
|
AndroidContact(name, ArrayList(uris), email, color, thumbnailUri, id, favorite)
|
||||||
}
|
|
||||||
when (this) {
|
|
||||||
is BaresipContact ->
|
|
||||||
(copy as BaresipContact).avatarImage = this.avatarImage
|
|
||||||
is AndroidContact ->
|
|
||||||
(copy as AndroidContact).uris.addAll(this.uris)
|
|
||||||
}
|
}
|
||||||
|
if (this is BaresipContact)
|
||||||
|
(copy as BaresipContact).avatarImage = this.avatarImage
|
||||||
return copy
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +164,8 @@ sealed class Contact {
|
|||||||
ContactsContract.Contacts.STARRED)
|
ContactsContract.Contacts.STARRED)
|
||||||
val selection =
|
val selection =
|
||||||
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
|
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + "' OR " +
|
||||||
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'"
|
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "' OR " +
|
||||||
|
ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'"
|
||||||
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
|
val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,
|
||||||
selection, null, null)
|
selection, null, null)
|
||||||
BaresipService.androidContacts.value = listOf()
|
BaresipService.androidContacts.value = listOf()
|
||||||
@@ -184,27 +174,33 @@ sealed class Contact {
|
|||||||
val id = cur.getLong(0)
|
val id = cur.getLong(0)
|
||||||
val name = cur.getString(1) ?: ""
|
val name = cur.getString(1) ?: ""
|
||||||
val mime = cur.getString(2)
|
val mime = cur.getString(2)
|
||||||
val data = cur.getString(3)
|
val data = cur.getString(3) ?: continue
|
||||||
val thumb = cur.getString(4)?.toUri()
|
val thumb = cur.getString(4)?.toUri()
|
||||||
val starred = cur.getInt(5)
|
val starred = cur.getInt(5)
|
||||||
val contact = if (contacts.containsKey(id))
|
val contact = if (contacts.containsKey(id))
|
||||||
contacts[id]!!
|
contacts[id]!!
|
||||||
else
|
else
|
||||||
AndroidContact(name, Utils.randomColor(), thumb, id, starred == 1)
|
AndroidContact(name, ArrayList<String>(), "", Utils.randomColor(), thumb, id, starred == 1)
|
||||||
if (contact.name == "" && name != "")
|
if (contact.name == "" && name != "")
|
||||||
contact.name = name
|
contact.name = name
|
||||||
if (contact.thumbnailUri == null && thumb != null)
|
if (contact.thumbnailUri == null && thumb != null)
|
||||||
contact.thumbnailUri = thumb
|
contact.thumbnailUri = thumb
|
||||||
if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {
|
when (mime) {
|
||||||
val uri = "tel:${data.filterNot { setOf('-', ' ', '(', ')').contains(it) }}"
|
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
|
||||||
if (uri !in contact.uris)
|
val uri = "tel:${data.filterNot { setOf('-', ' ', '(', ')').contains(it) }}"
|
||||||
contact.uris.add(uri)
|
if (uri !in contact.uris)
|
||||||
// contact.types.add(typeToString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)))
|
contact.uris.add(uri)
|
||||||
|
}
|
||||||
|
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
|
||||||
|
val uri = "sip:$data"
|
||||||
|
if (uri !in contact.uris)
|
||||||
|
contact.uris.add(uri)
|
||||||
|
}
|
||||||
|
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
|
||||||
|
if (contact.email == "")
|
||||||
|
contact.email = data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
|
|
||||||
contact.uris.add("sip:$data")
|
|
||||||
else
|
|
||||||
continue
|
|
||||||
if (!contacts.containsKey(id))
|
if (!contacts.containsKey(id))
|
||||||
contacts[id] = contact
|
contacts[id] = contact
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-40
@@ -2,8 +2,10 @@ package com.tutpro.baresip
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -18,14 +20,14 @@ import androidx.compose.foundation.layout.imePadding
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.Chat
|
import androidx.compose.material.icons.automirrored.filled.Chat
|
||||||
import androidx.compose.material.icons.outlined.Call
|
import androidx.compose.material.icons.outlined.Call
|
||||||
|
import androidx.compose.material.icons.outlined.Email
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -40,10 +42,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
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.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
@@ -52,10 +56,11 @@ import androidx.navigation.NavType
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
fun NavGraphBuilder.androidContactScreenRoute(navController: NavController, viewModel: ViewModel) {
|
fun NavGraphBuilder.contactScreenRoute(navController: NavController, viewModel: ViewModel) {
|
||||||
composable(
|
composable(
|
||||||
route = "android_contact/{name}",
|
route = "contact/{name}",
|
||||||
arguments = listOf(navArgument("name") { type = NavType.StringType })
|
arguments = listOf(navArgument("name") { type = NavType.StringType })
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
@@ -71,10 +76,11 @@ fun NavGraphBuilder.androidContactScreenRoute(navController: NavController, view
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContactScreen(ctx: Context, viewModel: ViewModel, navController: NavController, name: String) {
|
private fun ContactScreen(ctx: Context, viewModel: ViewModel, navController: NavController, name: String) {
|
||||||
val contact = Contact.androidContact(name)
|
val contact = Contact.baresipContact(name) ?: Contact.androidContact(name)
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
Log.e(TAG, "No Android contact found with name $name")
|
Log.e(TAG, "No contact found with name $name")
|
||||||
navController.navigateUp()
|
navController.navigateUp()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize().imePadding(),
|
modifier = Modifier.fillMaxSize().imePadding(),
|
||||||
@@ -90,7 +96,7 @@ private fun ContactScreen(ctx: Context, viewModel: ViewModel, navController: Nav
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
ContactContent(ctx, viewModel, navController, contentPadding, contact!!)
|
ContactContent(ctx, viewModel, navController, contentPadding, contact)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -123,19 +129,23 @@ private fun ContactContent(
|
|||||||
viewModel: ViewModel,
|
viewModel: ViewModel,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
contact: Contact.AndroidContact
|
contact: Contact
|
||||||
) {
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp),
|
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp)
|
||||||
|
.verticalScroll(scrollState),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Avatar(contact)
|
Avatar(contact)
|
||||||
ContactName(contact.name)
|
ContactName(contact.name())
|
||||||
Uris(ctx, viewModel, navController, contact)
|
Uris(ctx, viewModel, navController, contact)
|
||||||
|
if (contact.email().isNotEmpty())
|
||||||
|
Email(ctx, contact.email())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +163,19 @@ private fun TextAvatar(text: String, color: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ImageAvatar(uri: Uri) {
|
private fun ImageAvatar(bitmap: Bitmap) {
|
||||||
|
Image(
|
||||||
|
bitmap = bitmap.asImageBitmap(),
|
||||||
|
contentDescription = "Avatar",
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(avatarSize.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AsyncImageAvatar(uri: Uri) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = uri,
|
model = uri,
|
||||||
contentDescription = stringResource(R.string.avatar_image),
|
contentDescription = stringResource(R.string.avatar_image),
|
||||||
@@ -163,19 +185,28 @@ private fun ImageAvatar(uri: Uri) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Avatar(contact: Contact.AndroidContact) {
|
private fun Avatar(contact: Contact) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
val color = contact.color
|
val color = contact.colorInt()
|
||||||
val name = contact.name
|
val name = contact.name()
|
||||||
val thumbnailUri = contact.thumbnailUri
|
when (contact) {
|
||||||
if (thumbnailUri != null)
|
is Contact.BaresipContact -> {
|
||||||
ImageAvatar(thumbnailUri)
|
if (contact.avatarImage != null)
|
||||||
else
|
ImageAvatar(contact.avatarImage!!)
|
||||||
TextAvatar(if (name == "") "" else name[0].toString(), color)
|
else
|
||||||
|
TextAvatar(if (name == "") "" else name[0].toString(), color)
|
||||||
|
}
|
||||||
|
is Contact.AndroidContact -> {
|
||||||
|
if (contact.thumbnailUri != null)
|
||||||
|
AsyncImageAvatar(contact.thumbnailUri!!)
|
||||||
|
else
|
||||||
|
TextAvatar(if (name == "") "" else name[0].toString(), color)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +215,48 @@ private fun ContactName(name: String) {
|
|||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
|
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Email(ctx: Context, email: String) {
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth().padding(bottom = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
Text(name, fontSize = 24.sp, color = MaterialTheme.colorScheme.onBackground)
|
Text(
|
||||||
|
text = stringResource(R.string.bullet_item, email),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
|
||||||
|
data = "mailto:$email".toUri()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ctx.startActivity(emailIntent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to start email activity: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Email,
|
||||||
|
contentDescription = "Send Email",
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,23 +265,18 @@ private fun Uris(
|
|||||||
ctx: Context,
|
ctx: Context,
|
||||||
viewModel: ViewModel,
|
viewModel: ViewModel,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
contact: Contact.AndroidContact
|
contact: Contact
|
||||||
) {
|
) {
|
||||||
val lazyListState = rememberLazyListState()
|
Column(
|
||||||
LazyColumn(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 16.dp, end = 4.dp)
|
|
||||||
.background(MaterialTheme.colorScheme.background),
|
|
||||||
state = lazyListState,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
items(contact.uris) { uri ->
|
for (uri in contact.uris()) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = uri.substringAfter(":"),
|
text = stringResource(R.string.bullet_item, uri.substringAfter(":")),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
@@ -225,13 +290,18 @@ private fun Uris(
|
|||||||
if (ua == null)
|
if (ua == null)
|
||||||
Log.w(TAG, "Message clickable did not find AoR $aor")
|
Log.w(TAG, "Message clickable did not find AoR $aor")
|
||||||
else {
|
else {
|
||||||
val intent = Intent(ctx, MainActivity::class.java)
|
if (ua.account.isMobile && !Utils.isDefaultSmsApp(ctx)) {
|
||||||
intent.putExtra("uap", ua.uap)
|
handleDialog(ctx, ctx.getString(R.string.notice),
|
||||||
intent.putExtra("peer", uri)
|
ctx.getString(R.string.enable_default_messaging))
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
} else {
|
||||||
navController.navigate("main") {
|
val intent = Intent(ctx, MainActivity::class.java)
|
||||||
popUpTo("main") { inclusive = false }
|
intent.putExtra("uap", ua.uap)
|
||||||
launchSingleTop = true
|
intent.putExtra("peer", uri)
|
||||||
|
handleIntent(ctx, viewModel, intent, "message")
|
||||||
|
navController.navigate("main") {
|
||||||
|
popUpTo("main") { inclusive = false }
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,5 +342,3 @@ private fun Uris(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -84,25 +84,18 @@ import com.tutpro.baresip.CustomElements.TextAvatar
|
|||||||
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
const val avatarSize: Int = 96
|
const val avatarSize: Int = 96
|
||||||
|
|
||||||
fun NavGraphBuilder.contactsScreenRoute(
|
fun NavGraphBuilder.contactsScreenRoute(navController: NavController) {
|
||||||
navController: NavController,
|
|
||||||
viewModel: ViewModel
|
|
||||||
) {
|
|
||||||
composable("contacts") { _ ->
|
composable("contacts") { _ ->
|
||||||
ContactsScreen(navController = navController, viewModel = viewModel)
|
ContactsScreen(navController = navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContactsScreen(
|
private fun ContactsScreen(navController: NavController) {
|
||||||
navController: NavController,
|
|
||||||
viewModel: ViewModel
|
|
||||||
) {
|
|
||||||
|
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
val activity = LocalActivity.current!!
|
val activity = LocalActivity.current!!
|
||||||
@@ -429,7 +422,7 @@ private fun ContactsScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
ContactsContent(ctx, viewModel, navController, contentPadding, searchContactName)
|
ContactsContent(ctx, navController, contentPadding, searchContactName)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -497,31 +490,21 @@ private fun BottomBar(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ContactsContent(
|
private fun ContactsContent(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
viewModel: ViewModel,
|
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String
|
searchQuery: String
|
||||||
) {
|
) {
|
||||||
val showDialog = remember { mutableStateOf(false) }
|
val showConfirmationDialog = remember { mutableStateOf(false) }
|
||||||
val dialogMessage = remember { mutableStateOf("") }
|
val confirmationDialogMessage = remember { mutableStateOf("") }
|
||||||
val secondText = remember { mutableStateOf("") }
|
|
||||||
val secondAction = remember { mutableStateOf({}) }
|
|
||||||
val lastText = remember { mutableStateOf("") }
|
|
||||||
val lastAction = remember { mutableStateOf({}) }
|
val lastAction = remember { mutableStateOf({}) }
|
||||||
val thirdText = remember { mutableStateOf("") }
|
|
||||||
val thirdAction = remember { mutableStateOf({}) }
|
|
||||||
|
|
||||||
if (showDialog.value)
|
if (showConfirmationDialog.value)
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
showDialog = showDialog,
|
showDialog = showConfirmationDialog,
|
||||||
title = stringResource(R.string.confirmation),
|
title = stringResource(R.string.confirmation),
|
||||||
message = dialogMessage.value,
|
message = confirmationDialogMessage.value,
|
||||||
firstButtonText = stringResource(R.string.cancel),
|
firstButtonText = stringResource(R.string.cancel),
|
||||||
secondButtonText = secondText.value,
|
lastButtonText = stringResource(R.string.delete),
|
||||||
onSecondClicked = secondAction.value,
|
|
||||||
thirdButtonText = thirdText.value,
|
|
||||||
onThirdClicked = thirdAction.value,
|
|
||||||
lastButtonText = lastText.value,
|
|
||||||
onLastClicked = lastAction.value,
|
onLastClicked = lastAction.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -638,132 +621,13 @@ private fun ContactsContent(
|
|||||||
.padding(start = 10.dp)
|
.padding(start = 10.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
val aor = viewModel.selectedAor.value
|
navController.navigate("contact/${contact.name()}")
|
||||||
val ua = UserAgent.ofAor(aor)
|
|
||||||
val intent = Intent(ctx, MainActivity::class.java)
|
|
||||||
if (ua != null) {
|
|
||||||
intent.putExtra("uap", ua.uap)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log.w(TAG, "onClickListener did not find UA for $aor")
|
|
||||||
val peerName = contact.name()
|
|
||||||
if (contact.email.isNotEmpty())
|
|
||||||
dialogMessage.value = String.format(
|
|
||||||
ctx.getString(R.string.contact_email_action_question),
|
|
||||||
peerName
|
|
||||||
)
|
|
||||||
else
|
|
||||||
dialogMessage.value = String.format(
|
|
||||||
ctx.getString(R.string.contact_action_question),
|
|
||||||
peerName
|
|
||||||
)
|
|
||||||
secondText.value = ctx.getString(R.string.call)
|
|
||||||
secondAction.value = {
|
|
||||||
if (ua != null) {
|
|
||||||
val uris = if (ua.account.isMobile)
|
|
||||||
contact.uris.filter { it.startsWith("tel:") }
|
|
||||||
else
|
|
||||||
contact.uris
|
|
||||||
if (uris.size > 1) {
|
|
||||||
CustomElements.selectItems.value = uris
|
|
||||||
CustomElements.selectItemAction.value = { index: Int ->
|
|
||||||
intent.putExtra("peer", uris[index])
|
|
||||||
handleIntent(ctx, viewModel, intent, "call")
|
|
||||||
navController.navigate("main") {
|
|
||||||
popUpTo("main")
|
|
||||||
launchSingleTop = true
|
|
||||||
}
|
|
||||||
CustomElements.showSelectItemDialog.value = false
|
|
||||||
}
|
|
||||||
CustomElements.showSelectItemDialog.value = true
|
|
||||||
}
|
|
||||||
else if (uris.size == 1) {
|
|
||||||
intent.putExtra("peer", uris[0])
|
|
||||||
handleIntent(ctx, viewModel, intent, "call")
|
|
||||||
navController.navigate("main") {
|
|
||||||
popUpTo("main")
|
|
||||||
launchSingleTop = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
|
||||||
alertMessage.value = ctx.getString(R.string.no_telephone_number)
|
|
||||||
showAlert.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thirdText.value = ctx.getString(R.string.send_message)
|
|
||||||
thirdAction.value = {
|
|
||||||
if (ua != null) {
|
|
||||||
val uris = if (ua.account.isMobile)
|
|
||||||
contact.uris.filter { it.startsWith("tel:") }
|
|
||||||
else
|
|
||||||
contact.uris
|
|
||||||
|
|
||||||
if (uris.size > 1) {
|
|
||||||
CustomElements.selectItems.value = uris
|
|
||||||
CustomElements.selectItemAction.value = { index: Int ->
|
|
||||||
intent.putExtra("peer", uris[index])
|
|
||||||
if (ua.account.isMobile) {
|
|
||||||
if (!Utils.isDefaultSmsApp(ctx)) {
|
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
|
||||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
|
||||||
showAlert.value = true
|
|
||||||
} else {
|
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
CustomElements.showSelectItemDialog.value = false
|
|
||||||
}
|
|
||||||
CustomElements.showSelectItemDialog.value = true
|
|
||||||
} else if (uris.size == 1) {
|
|
||||||
intent.putExtra("peer", uris[0])
|
|
||||||
if (ua.account.isMobile) {
|
|
||||||
if (!Utils.isDefaultSmsApp(ctx)) {
|
|
||||||
alertTitle.value = ctx.getString(R.string.notice)
|
|
||||||
alertMessage.value = ctx.getString(R.string.enable_default_messaging)
|
|
||||||
showAlert.value = true
|
|
||||||
} else {
|
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
handleIntent(ctx, viewModel, intent, "message")
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contact.email.isNotEmpty()) {
|
|
||||||
lastText.value = ctx.getString(R.string.send_email)
|
|
||||||
lastAction.value = {
|
|
||||||
val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
|
|
||||||
data = "mailto:${contact.email}".toUri()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ctx.startActivity(emailIntent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to start email activity: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastText.value = ""
|
|
||||||
}
|
|
||||||
showDialog.value = true
|
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
dialogMessage.value = String.format(
|
confirmationDialogMessage.value = String.format(
|
||||||
ctx.getString(R.string.contact_delete_question),
|
ctx.getString(R.string.contact_delete_question),
|
||||||
contact.name()
|
contact.name()
|
||||||
)
|
)
|
||||||
secondText.value = ""
|
|
||||||
lastText.value = ctx.getString(R.string.delete)
|
|
||||||
lastAction.value = {
|
lastAction.value = {
|
||||||
val id = contact.id
|
val id = contact.id
|
||||||
val avatarFile = File(
|
val avatarFile = File(
|
||||||
@@ -782,7 +646,7 @@ private fun ContactsContent(
|
|||||||
}
|
}
|
||||||
Contact.removeBaresipContact(contact)
|
Contact.removeBaresipContact(contact)
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showConfirmationDialog.value = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -808,14 +672,14 @@ private fun ContactsContent(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
|
.padding(start = 10.dp, top = 4.dp, bottom = 4.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = { navController.navigate("android_contact/${contact.name()}") },
|
onClick = {
|
||||||
|
navController.navigate("contact/${contact.name()}")
|
||||||
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
dialogMessage.value = String.format(
|
confirmationDialogMessage.value = String.format(
|
||||||
ctx.getString(R.string.contact_delete_question),
|
ctx.getString(R.string.contact_delete_question),
|
||||||
contact.name()
|
contact.name()
|
||||||
)
|
)
|
||||||
secondText.value = ""
|
|
||||||
lastText.value = ctx.getString(R.string.delete)
|
|
||||||
lastAction.value = {
|
lastAction.value = {
|
||||||
ctx.contentResolver.delete(
|
ctx.contentResolver.delete(
|
||||||
ContactsContract.RawContacts.CONTENT_URI,
|
ContactsContract.RawContacts.CONTENT_URI,
|
||||||
@@ -823,7 +687,7 @@ private fun ContactsContent(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showDialog.value = true
|
showConfirmationDialog.value = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -285,9 +285,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
audioScreenRoute(navController)
|
audioScreenRoute(navController)
|
||||||
accountScreenRoute(navController)
|
accountScreenRoute(navController)
|
||||||
codecsScreenRoute(navController)
|
codecsScreenRoute(navController)
|
||||||
contactsScreenRoute(navController, viewModel)
|
contactsScreenRoute(navController)
|
||||||
baresipContactScreenRoute(navController)
|
baresipContactScreenRoute(navController)
|
||||||
androidContactScreenRoute(navController, viewModel)
|
contactScreenRoute(navController, viewModel)
|
||||||
callsScreenRoute(navController, viewModel)
|
callsScreenRoute(navController, viewModel)
|
||||||
callDetailsScreenRoute(navController, viewModel)
|
callDetailsScreenRoute(navController, viewModel)
|
||||||
blockedScreenRoute(navController)
|
blockedScreenRoute(navController)
|
||||||
|
|||||||
Reference in New Issue
Block a user