Added contact export as vCard from contacts list

This commit is contained in:
Sylvain Berfini 2023-08-27 12:10:32 +02:00
parent 855b03fb34
commit 5af3a490d4
6 changed files with 99 additions and 14 deletions

View file

@ -93,6 +93,7 @@ class ContactFragment : GenericFragment() {
}
binding.setShareClickListener {
Log.i("$TAG Sharing friend, exporting it as vCard file first")
viewModel.exportContactAsVCard()
}
@ -173,9 +174,13 @@ class ContactFragment : GenericFragment() {
}
viewModel.vCardTerminatedEvent.observe(viewLifecycleOwner) {
it.consume { file ->
Log.i("$TAG Friend was exported as vCard file [${file.absolutePath}]")
shareContact(file)
it.consume { pair ->
val contactName = pair.first
val file = pair.second
Log.i(
"$TAG Friend [$contactName] was exported as vCard file [${file.absolutePath}], sharing it"
)
shareContact(contactName, file)
}
}
@ -203,7 +208,7 @@ class ContactFragment : GenericFragment() {
)
}
private fun shareContact(file: File) {
private fun shareContact(name: String, file: File) {
val publicUri = FileProvider.getUriForFile(
requireContext(),
requireContext().getString(R.string.file_provider),
@ -214,7 +219,7 @@ class ContactFragment : GenericFragment() {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, publicUri)
putExtra(Intent.EXTRA_SUBJECT, viewModel.contact.value?.friend?.name)
putExtra(Intent.EXTRA_SUBJECT, name)
type = ContactsContract.Contacts.CONTENT_VCARD_TYPE
}

View file

@ -19,17 +19,21 @@
*/
package org.linphone.ui.main.contacts.fragment
import android.content.Intent
import android.os.Bundle
import android.provider.ContactsContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.annotation.UiThread
import androidx.core.content.FileProvider
import androidx.core.view.doOnPreDraw
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import java.io.File
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.tools.Log
@ -115,6 +119,17 @@ class ContactsListFragment : GenericFragment() {
Log.i("$TAG Favourites contacts list is ready with [${it.size}] items")
}
listViewModel.vCardTerminatedEvent.observe(viewLifecycleOwner) {
it.consume { pair ->
val contactName = pair.first
val file = pair.second
Log.i(
"$TAG Friend [$contactName] was exported as vCard file [${file.absolutePath}], sharing it"
)
shareContact(contactName, file)
}
}
sharedViewModel.searchFilter.observe(viewLifecycleOwner) {
it.consume { filter ->
listViewModel.applyFilter(filter)
@ -130,7 +145,7 @@ class ContactsListFragment : GenericFragment() {
adapter.contactLongClickedEvent.observe(viewLifecycleOwner) {
it.consume { model ->
val modalBottomSheet = ContactsListMenuDialogFragment(
model.friend.starred,
model.starred,
{ // onDismiss
adapter.resetSelection()
},
@ -147,8 +162,10 @@ class ContactsListFragment : GenericFragment() {
}
},
{ // onShare
Log.i("$TAG Sharing friend [${model.name.value}]")
// TODO
Log.i(
"$TAG Sharing friend [${model.name.value}], exporting it as vCard file first"
)
listViewModel.exportContactAsVCard(model.friend)
},
{ // onDelete
coreContext.postOnCoreThread {
@ -168,4 +185,23 @@ class ContactsListFragment : GenericFragment() {
}
}
}
private fun shareContact(name: String, file: File) {
val publicUri = FileProvider.getUriForFile(
requireContext(),
requireContext().getString(R.string.file_provider),
file
)
Log.i("$TAG Public URI for vCard file is [$publicUri], starting intent chooser")
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, publicUri)
putExtra(Intent.EXTRA_SUBJECT, name)
type = ContactsContract.Contacts.CONTENT_VCARD_TYPE
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
}

View file

@ -37,6 +37,8 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) {
val id = friend.refKey
val starred = friend.starred
val avatar = MutableLiveData<Uri>()
val initials = LinphoneUtils.getInitials(friend.name.orEmpty())

View file

@ -80,8 +80,8 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<String>>()
}
val vCardTerminatedEvent: MutableLiveData<Event<File>> by lazy {
MutableLiveData<Event<File>>()
val vCardTerminatedEvent: MutableLiveData<Event<Pair<String, File>>> by lazy {
MutableLiveData<Event<Pair<String, File>>>()
}
val displayTrustProcessDialogEvent: MutableLiveData<Event<Boolean>> by lazy {
@ -287,11 +287,14 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
Locale.getDefault()
)
val file = FileUtils.getFileStorageCacheDir("$fileName.vcf")
val file = FileUtils.getFileStorageCacheDir(
"$fileName.vcf",
overrideExisting = true
)
viewModelScope.launch {
if (FileUtils.dumpStringToFile(vCard, file)) {
Log.i("$TAG vCard string saved as file in cache folder")
vCardTerminatedEvent.postValue(Event(file))
vCardTerminatedEvent.postValue(Event(Pair(friend.name.orEmpty(), file)))
} else {
Log.e("$TAG Failed to save vCard string as file in cache folder")
}

View file

@ -23,7 +23,11 @@ import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import java.io.File
import java.util.ArrayList
import java.util.Locale
import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.contacts.ContactsListener
@ -34,6 +38,8 @@ import org.linphone.core.SearchResult
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.model.isInSecureMode
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
class ContactsListViewModel @UiThread constructor() : ViewModel() {
companion object {
@ -48,6 +54,10 @@ class ContactsListViewModel @UiThread constructor() : ViewModel() {
val isListFiltered = MutableLiveData<Boolean>()
val vCardTerminatedEvent: MutableLiveData<Event<Pair<String, File>>> by lazy {
MutableLiveData<Event<Pair<String, File>>>()
}
private var currentFilter = ""
private var previousFilter = "NotSet"
private var limitSearchToLinphoneAccounts = true
@ -160,6 +170,33 @@ class ContactsListViewModel @UiThread constructor() : ViewModel() {
}
}
@UiThread
fun exportContactAsVCard(friend: Friend) {
coreContext.postOnCoreThread {
val vCard = friend.vcard?.asVcard4String()
if (!vCard.isNullOrEmpty()) {
Log.i("$TAG Friend has been successfully dumped as vCard string")
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
Locale.getDefault()
)
val file = FileUtils.getFileStorageCacheDir(
"$fileName.vcf",
overrideExisting = true
)
viewModelScope.launch {
if (FileUtils.dumpStringToFile(vCard, file)) {
Log.i("$TAG vCard string saved as file in cache folder")
vCardTerminatedEvent.postValue(Event(Pair(friend.name.orEmpty(), file)))
} else {
Log.e("$TAG Failed to save vCard string as file in cache folder")
}
}
} else {
Log.e("$TAG Failed to dump contact as vCard string")
}
}
}
@WorkerThread
private fun applyFilter(
filter: String,

View file

@ -39,6 +39,8 @@ import org.linphone.core.tools.Log
class LinphoneUtils {
companion object {
private const val TAG = "[App Utils]"
@AnyThread
fun getFirstLetter(displayName: String): String {
return getInitials(displayName, 1)
@ -62,7 +64,7 @@ class LinphoneUtils {
) {
val glyph = emoji.process(split[i])
if (characters > 0) { // Limit initial to 1 emoji only
Log.d("[App Utils] We limit initials to one emoji only")
Log.d("$TAG We limit initials to one emoji only")
initials = ""
}
initials += glyph
@ -71,7 +73,7 @@ class LinphoneUtils {
initials += split[i][0]
}
} catch (ise: IllegalStateException) {
Log.e("[App Utils] Can't call hasEmojiGlyph: $ise")
Log.e("$TAG Can't call hasEmojiGlyph: $ise")
initials += split[i][0]
}