mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-05-03 06:46:25 +00:00
Added contact export as vCard from contacts list
This commit is contained in:
parent
855b03fb34
commit
5af3a490d4
6 changed files with 99 additions and 14 deletions
|
|
@ -93,6 +93,7 @@ class ContactFragment : GenericFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.setShareClickListener {
|
binding.setShareClickListener {
|
||||||
|
Log.i("$TAG Sharing friend, exporting it as vCard file first")
|
||||||
viewModel.exportContactAsVCard()
|
viewModel.exportContactAsVCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,9 +174,13 @@ class ContactFragment : GenericFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.vCardTerminatedEvent.observe(viewLifecycleOwner) {
|
viewModel.vCardTerminatedEvent.observe(viewLifecycleOwner) {
|
||||||
it.consume { file ->
|
it.consume { pair ->
|
||||||
Log.i("$TAG Friend was exported as vCard file [${file.absolutePath}]")
|
val contactName = pair.first
|
||||||
shareContact(file)
|
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(
|
val publicUri = FileProvider.getUriForFile(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
requireContext().getString(R.string.file_provider),
|
requireContext().getString(R.string.file_provider),
|
||||||
|
|
@ -214,7 +219,7 @@ class ContactFragment : GenericFragment() {
|
||||||
val sendIntent: Intent = Intent().apply {
|
val sendIntent: Intent = Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
putExtra(Intent.EXTRA_STREAM, publicUri)
|
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
|
type = ContactsContract.Contacts.CONTENT_VCARD_TYPE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,21 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.ui.main.contacts.fragment
|
package org.linphone.ui.main.contacts.fragment
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.ContactsContract
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.Animation
|
import android.view.animation.Animation
|
||||||
import android.view.animation.AnimationUtils
|
import android.view.animation.AnimationUtils
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.doOnPreDraw
|
import androidx.core.view.doOnPreDraw
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.navGraphViewModels
|
import androidx.navigation.navGraphViewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import java.io.File
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.R
|
import org.linphone.R
|
||||||
import org.linphone.core.tools.Log
|
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")
|
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) {
|
sharedViewModel.searchFilter.observe(viewLifecycleOwner) {
|
||||||
it.consume { filter ->
|
it.consume { filter ->
|
||||||
listViewModel.applyFilter(filter)
|
listViewModel.applyFilter(filter)
|
||||||
|
|
@ -130,7 +145,7 @@ class ContactsListFragment : GenericFragment() {
|
||||||
adapter.contactLongClickedEvent.observe(viewLifecycleOwner) {
|
adapter.contactLongClickedEvent.observe(viewLifecycleOwner) {
|
||||||
it.consume { model ->
|
it.consume { model ->
|
||||||
val modalBottomSheet = ContactsListMenuDialogFragment(
|
val modalBottomSheet = ContactsListMenuDialogFragment(
|
||||||
model.friend.starred,
|
model.starred,
|
||||||
{ // onDismiss
|
{ // onDismiss
|
||||||
adapter.resetSelection()
|
adapter.resetSelection()
|
||||||
},
|
},
|
||||||
|
|
@ -147,8 +162,10 @@ class ContactsListFragment : GenericFragment() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ // onShare
|
{ // onShare
|
||||||
Log.i("$TAG Sharing friend [${model.name.value}]")
|
Log.i(
|
||||||
// TODO
|
"$TAG Sharing friend [${model.name.value}], exporting it as vCard file first"
|
||||||
|
)
|
||||||
|
listViewModel.exportContactAsVCard(model.friend)
|
||||||
},
|
},
|
||||||
{ // onDelete
|
{ // onDelete
|
||||||
coreContext.postOnCoreThread {
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ class ContactAvatarModel @WorkerThread constructor(val friend: Friend) {
|
||||||
|
|
||||||
val id = friend.refKey
|
val id = friend.refKey
|
||||||
|
|
||||||
|
val starred = friend.starred
|
||||||
|
|
||||||
val avatar = MutableLiveData<Uri>()
|
val avatar = MutableLiveData<Uri>()
|
||||||
|
|
||||||
val initials = LinphoneUtils.getInitials(friend.name.orEmpty())
|
val initials = LinphoneUtils.getInitials(friend.name.orEmpty())
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
||||||
MutableLiveData<Event<String>>()
|
MutableLiveData<Event<String>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
val vCardTerminatedEvent: MutableLiveData<Event<File>> by lazy {
|
val vCardTerminatedEvent: MutableLiveData<Event<Pair<String, File>>> by lazy {
|
||||||
MutableLiveData<Event<File>>()
|
MutableLiveData<Event<Pair<String, File>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
val displayTrustProcessDialogEvent: MutableLiveData<Event<Boolean>> by lazy {
|
val displayTrustProcessDialogEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||||
|
|
@ -287,11 +287,14 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
||||||
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
|
val fileName = friend.name.orEmpty().replace(" ", "_").toLowerCase(
|
||||||
Locale.getDefault()
|
Locale.getDefault()
|
||||||
)
|
)
|
||||||
val file = FileUtils.getFileStorageCacheDir("$fileName.vcf")
|
val file = FileUtils.getFileStorageCacheDir(
|
||||||
|
"$fileName.vcf",
|
||||||
|
overrideExisting = true
|
||||||
|
)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (FileUtils.dumpStringToFile(vCard, file)) {
|
if (FileUtils.dumpStringToFile(vCard, file)) {
|
||||||
Log.i("$TAG vCard string saved as file in cache folder")
|
Log.i("$TAG vCard string saved as file in cache folder")
|
||||||
vCardTerminatedEvent.postValue(Event(file))
|
vCardTerminatedEvent.postValue(Event(Pair(friend.name.orEmpty(), file)))
|
||||||
} else {
|
} else {
|
||||||
Log.e("$TAG Failed to save vCard string as file in cache folder")
|
Log.e("$TAG Failed to save vCard string as file in cache folder")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ import androidx.annotation.UiThread
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import java.io.File
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||||
import org.linphone.contacts.ContactsListener
|
import org.linphone.contacts.ContactsListener
|
||||||
|
|
@ -34,6 +38,8 @@ import org.linphone.core.SearchResult
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
import org.linphone.ui.main.contacts.model.ContactAvatarModel
|
||||||
import org.linphone.ui.main.model.isInSecureMode
|
import org.linphone.ui.main.model.isInSecureMode
|
||||||
|
import org.linphone.utils.Event
|
||||||
|
import org.linphone.utils.FileUtils
|
||||||
|
|
||||||
class ContactsListViewModel @UiThread constructor() : ViewModel() {
|
class ContactsListViewModel @UiThread constructor() : ViewModel() {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -48,6 +54,10 @@ class ContactsListViewModel @UiThread constructor() : ViewModel() {
|
||||||
|
|
||||||
val isListFiltered = MutableLiveData<Boolean>()
|
val isListFiltered = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val vCardTerminatedEvent: MutableLiveData<Event<Pair<String, File>>> by lazy {
|
||||||
|
MutableLiveData<Event<Pair<String, File>>>()
|
||||||
|
}
|
||||||
|
|
||||||
private var currentFilter = ""
|
private var currentFilter = ""
|
||||||
private var previousFilter = "NotSet"
|
private var previousFilter = "NotSet"
|
||||||
private var limitSearchToLinphoneAccounts = true
|
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
|
@WorkerThread
|
||||||
private fun applyFilter(
|
private fun applyFilter(
|
||||||
filter: String,
|
filter: String,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ import org.linphone.core.tools.Log
|
||||||
|
|
||||||
class LinphoneUtils {
|
class LinphoneUtils {
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "[App Utils]"
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getFirstLetter(displayName: String): String {
|
fun getFirstLetter(displayName: String): String {
|
||||||
return getInitials(displayName, 1)
|
return getInitials(displayName, 1)
|
||||||
|
|
@ -62,7 +64,7 @@ class LinphoneUtils {
|
||||||
) {
|
) {
|
||||||
val glyph = emoji.process(split[i])
|
val glyph = emoji.process(split[i])
|
||||||
if (characters > 0) { // Limit initial to 1 emoji only
|
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 = ""
|
||||||
}
|
}
|
||||||
initials += glyph
|
initials += glyph
|
||||||
|
|
@ -71,7 +73,7 @@ class LinphoneUtils {
|
||||||
initials += split[i][0]
|
initials += split[i][0]
|
||||||
}
|
}
|
||||||
} catch (ise: IllegalStateException) {
|
} catch (ise: IllegalStateException) {
|
||||||
Log.e("[App Utils] Can't call hasEmojiGlyph: $ise")
|
Log.e("$TAG Can't call hasEmojiGlyph: $ise")
|
||||||
initials += split[i][0]
|
initials += split[i][0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue