diff --git a/app/src/main/java/com/bnyro/contacts/domain/model/ContactData.kt b/app/src/main/java/com/bnyro/contacts/domain/model/ContactData.kt index 92a8a6fa..3813548b 100644 --- a/app/src/main/java/com/bnyro/contacts/domain/model/ContactData.kt +++ b/app/src/main/java/com/bnyro/contacts/domain/model/ContactData.kt @@ -1,6 +1,7 @@ package com.bnyro.contacts.domain.model import android.graphics.Bitmap +import android.net.Uri import com.bnyro.contacts.domain.enums.SortOrder data class ContactData( @@ -24,7 +25,8 @@ data class ContactData( var events: List = listOf(), var notes: List = listOf(), var groups: List = listOf(), - var websites: List = listOf() + var websites: List = listOf(), + var ringTone: Uri? = null ) { val accountIdentifier get() = "$accountType|$accountName" fun getNameBySortOrder(sortOrder: SortOrder): String? { diff --git a/app/src/main/java/com/bnyro/contacts/domain/repositories/DeviceContactsRepository.kt b/app/src/main/java/com/bnyro/contacts/domain/repositories/DeviceContactsRepository.kt index 885b2aa5..b5cff7fe 100644 --- a/app/src/main/java/com/bnyro/contacts/domain/repositories/DeviceContactsRepository.kt +++ b/app/src/main/java/com/bnyro/contacts/domain/repositories/DeviceContactsRepository.kt @@ -6,6 +6,7 @@ import android.annotation.SuppressLint import android.content.ContentProviderOperation import android.content.ContentResolver import android.content.ContentUris +import android.content.ContentValues import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -39,6 +40,7 @@ import com.bnyro.contacts.util.extension.stringValue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext + class DeviceContactsRepository(private val context: Context) : ContactsRepository { override val label: String = context.getString(R.string.device) @@ -126,7 +128,11 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor ContactsHelper.contactAttributesTypes.forEach { attribute -> if (attribute is StringAttribute) { - val dataStr = getEntry(contactId, attribute.androidContentType, attribute.androidValueColumn) + val dataStr = getEntry( + contactId, + attribute.androidContentType, + attribute.androidValueColumn + ) attribute.set(this, dataStr) } else if (attribute is ListAttribute) { val dataEntries = getExtras( @@ -317,22 +323,28 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor it.rowId.toString() ) }.toTypedArray(), - *ContactsHelper.contactAttributesTypes.filterIsInstance().map { attribute -> - attribute.get(contact)?.let { - getInsertAction(attribute.androidContentType, attribute.androidValueColumn, it) - } - }.toTypedArray(), - *ContactsHelper.contactAttributesTypes.filterIsInstance().map { attribute -> - attribute.get(contact).map { - getInsertAction( - attribute.androidContentType, - attribute.androidValueColumn, - it.value, - attribute.androidTypeColumn, - it.type - ) - } - }.flatten().toTypedArray() + *ContactsHelper.contactAttributesTypes.filterIsInstance() + .map { attribute -> + attribute.get(contact)?.let { + getInsertAction( + attribute.androidContentType, + attribute.androidValueColumn, + it + ) + } + }.toTypedArray(), + *ContactsHelper.contactAttributesTypes.filterIsInstance() + .map { attribute -> + attribute.get(contact).map { + getInsertAction( + attribute.androidContentType, + attribute.androidValueColumn, + it.value, + attribute.androidTypeColumn, + it.type + ) + } + }.flatten().toTypedArray() ).let { ArrayList(it) } contentResolver.applyBatch(AUTHORITY, ops) @@ -357,15 +369,19 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor for (attribute in ContactsHelper.contactAttributesTypes) { if (attribute is StringAttribute) { - operations.addAll(getUpdateSingleAction( - rawContactId, attribute.androidContentType, - attribute.androidValueColumn, attribute.get(contact) - )) + operations.addAll( + getUpdateSingleAction( + rawContactId, attribute.androidContentType, + attribute.androidValueColumn, attribute.get(contact) + ) + ) } else if (attribute is ListAttribute) { - operations.addAll(getUpdateMultipleAction( - rawContactId, attribute.androidContentType, attribute.get(contact), - attribute.androidValueColumn, attribute.androidTypeColumn - )) + operations.addAll( + getUpdateMultipleAction( + rawContactId, attribute.androidContentType, attribute.get(contact), + attribute.androidValueColumn, attribute.androidTypeColumn + ) + ) } } @@ -398,13 +414,19 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor fun getAccountTypes(): List { val accounts = AccountManager.get(context).accounts.filter { - ContentResolver.getIsSyncable(it, authority) > 0 && ContentResolver.getSyncAutomatically(it, authority) + ContentResolver.getIsSyncable( + it, + authority + ) > 0 && ContentResolver.getSyncAutomatically(it, authority) } return listOf(AccountType.androidDefault) + accounts.map { AccountType(it.name, it.type) } } - private fun getCreateAction(accountType: String, accountName: String): ContentProviderOperation { + private fun getCreateAction( + accountType: String, + accountName: String + ): ContentProviderOperation { return ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, accountType) .withValue(RawContacts.ACCOUNT_NAME, accountName) @@ -530,6 +552,17 @@ class DeviceContactsRepository(private val context: Context) : ContactsRepositor }.build() } + suspend fun updateContactRingTone(contactId: String, ringtoneUri: Uri) = + withContext(Dispatchers.IO) { + val contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, contactId) + + val values = ContentValues().apply { + put(Contacts.CUSTOM_RINGTONE, ringtoneUri.toString()) + } + + contentResolver.update(contactUri, values, null, null) + } + companion object { const val MAX_PHOTO_SIZE = 700f } diff --git a/app/src/main/java/com/bnyro/contacts/presentation/screens/contact/SingleContactScreen.kt b/app/src/main/java/com/bnyro/contacts/presentation/screens/contact/SingleContactScreen.kt index 71c0cdc7..2008adb1 100644 --- a/app/src/main/java/com/bnyro/contacts/presentation/screens/contact/SingleContactScreen.kt +++ b/app/src/main/java/com/bnyro/contacts/presentation/screens/contact/SingleContactScreen.kt @@ -1,5 +1,7 @@ package com.bnyro.contacts.presentation.screens.contact +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.launch import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -23,6 +25,9 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Message import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Shortcut +import androidx.compose.material.icons.rounded.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -65,6 +70,7 @@ import com.bnyro.contacts.presentation.screens.editor.components.ContactEntryGro import com.bnyro.contacts.presentation.screens.editor.components.ContactEntryTextGroup import com.bnyro.contacts.util.ContactsHelper import com.bnyro.contacts.util.IntentHelper +import com.bnyro.contacts.util.RingtonePickContract @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -86,6 +92,13 @@ fun SingleContactScreen(contact: ContactData, viewModel: ContactsModel, onClose: mutableStateOf(false) } + val ringtonePicker = + rememberLauncherForActivityResult(contract = RingtonePickContract()) { uri -> + if (uri != null) { + viewModel.updateContactRingTone(contact, uri) + } + } + FullScreenDialog(onClose = onClose) { Scaffold( topBar = { @@ -118,6 +131,30 @@ fun SingleContactScreen(contact: ContactData, viewModel: ContactsModel, onClose: ) { showDelete = true } + Box { + var showMore by remember { mutableStateOf(false) } + ClickableIcon( + icon = Icons.Rounded.MoreVert, + contentDescription = R.string.more + ) { + showMore = !showMore + } + DropdownMenu( + expanded = showMore, + onDismissRequest = { + showMore = false + } + ) { + DropdownMenuItem( + text = { + Text(text = stringResource(R.string.change_ringtone)) + }, + onClick = { + ringtonePicker.launch() + } + ) + } + } } ) } diff --git a/app/src/main/java/com/bnyro/contacts/presentation/screens/contacts/model/ContactsModel.kt b/app/src/main/java/com/bnyro/contacts/presentation/screens/contacts/model/ContactsModel.kt index ee6b97a9..f7c13627 100644 --- a/app/src/main/java/com/bnyro/contacts/presentation/screens/contacts/model/ContactsModel.kt +++ b/app/src/main/java/com/bnyro/contacts/presentation/screens/contacts/model/ContactsModel.kt @@ -223,6 +223,12 @@ class ContactsModel( } } + fun updateContactRingTone(contact: ContactData, uri: Uri) { + viewModelScope.launch { + deviceContactsRepository.updateContactRingTone(contact.contactId.toString(), uri) + } + } + companion object { val Factory = viewModelFactory { initializer { diff --git a/app/src/main/java/com/bnyro/contacts/util/RingtonePickContract.kt b/app/src/main/java/com/bnyro/contacts/util/RingtonePickContract.kt new file mode 100644 index 00000000..1b9332d1 --- /dev/null +++ b/app/src/main/java/com/bnyro/contacts/util/RingtonePickContract.kt @@ -0,0 +1,26 @@ +package com.bnyro.contacts.util + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.RingtoneManager +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContract +import com.bnyro.contacts.R + +class RingtonePickContract : ActivityResultContract() { + override fun createIntent(context: Context, input: Void?): Intent { + return Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { + putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE) + putExtra( + RingtoneManager.EXTRA_RINGTONE_TITLE, + context.getString(R.string.select_custom_ringtone) + ) + } + } + + override fun parseResult(resultCode: Int, intent: Intent?): Uri? { + return intent.takeIf { resultCode == Activity.RESULT_OK } + ?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 94e7044d..f118fa41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,4 +149,6 @@ Dialing... Call From Keypad + Select custom ringtone + Change ringtone \ No newline at end of file