Fixed contact sharing in chat + reworked how file picker is created
This commit is contained in:
parent
beb9d0db45
commit
2a2315e944
6 changed files with 171 additions and 112 deletions
|
@ -464,7 +464,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
for (fileToUploadPath in ImageUtils.getFilesPathFromPickerIntent(
|
for (fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
|
||||||
data,
|
data,
|
||||||
chatSendingViewModel.temporaryFileUploadPath
|
chatSendingViewModel.temporaryFileUploadPath
|
||||||
)) {
|
)) {
|
||||||
|
@ -617,13 +617,11 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pickFile() {
|
private fun pickFile() {
|
||||||
val cameraIntents = ArrayList<Intent>()
|
val intentsList = ArrayList<Intent>()
|
||||||
|
|
||||||
// Handles image & video picking
|
val pickerIntent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
val galleryIntent = Intent(Intent.ACTION_PICK)
|
pickerIntent.type = "*/*"
|
||||||
galleryIntent.type = "*/*"
|
pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
|
|
||||||
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
||||||
|
|
||||||
if (PermissionHelper.get().hasCameraPermission()) {
|
if (PermissionHelper.get().hasCameraPermission()) {
|
||||||
// Allows to capture directly from the camera
|
// Allows to capture directly from the camera
|
||||||
|
@ -639,25 +637,17 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri)
|
capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri)
|
||||||
capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
cameraIntents.add(capturePictureIntent)
|
intentsList.add(capturePictureIntent)
|
||||||
|
|
||||||
val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
|
val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
|
||||||
cameraIntents.add(captureVideoIntent)
|
intentsList.add(captureVideoIntent)
|
||||||
}
|
|
||||||
|
|
||||||
if (PermissionHelper.get().hasReadExternalStorage()) {
|
|
||||||
// Finally allow any kind of file
|
|
||||||
val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
|
|
||||||
fileIntent.type = "*/*"
|
|
||||||
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
||||||
cameraIntents.add(fileIntent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val chooserIntent =
|
val chooserIntent =
|
||||||
Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog))
|
Intent.createChooser(pickerIntent, getString(R.string.chat_message_pick_file_dialog))
|
||||||
chooserIntent.putExtra(
|
chooserIntent.putExtra(
|
||||||
Intent.EXTRA_INITIAL_INTENTS,
|
Intent.EXTRA_INITIAL_INTENTS,
|
||||||
cameraIntents.toArray(arrayOf<Parcelable>())
|
intentsList.toArray(arrayOf<Parcelable>())
|
||||||
)
|
)
|
||||||
|
|
||||||
startActivityForResult(chooserIntent, 0)
|
startActivityForResult(chooserIntent, 0)
|
||||||
|
|
|
@ -46,7 +46,6 @@ import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.ContactEditorFragmentBinding
|
import org.linphone.databinding.ContactEditorFragmentBinding
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.ImageUtils
|
|
||||||
import org.linphone.utils.PermissionHelper
|
import org.linphone.utils.PermissionHelper
|
||||||
|
|
||||||
class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener {
|
class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener {
|
||||||
|
@ -146,7 +145,7 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
||||||
if (contactImageFilePath != null) {
|
if (contactImageFilePath != null) {
|
||||||
viewModel.setPictureFromPath(contactImageFilePath)
|
viewModel.setPictureFromPath(contactImageFilePath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.SideMenuFragmentBinding
|
import org.linphone.databinding.SideMenuFragmentBinding
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.ImageUtils
|
|
||||||
import org.linphone.utils.PermissionHelper
|
import org.linphone.utils.PermissionHelper
|
||||||
|
|
||||||
class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
||||||
|
@ -121,7 +120,7 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
|
||||||
if (contactImageFilePath != null) {
|
if (contactImageFilePath != null) {
|
||||||
viewModel.setPictureFromPath(contactImageFilePath)
|
viewModel.setPictureFromPath(contactImageFilePath)
|
||||||
}
|
}
|
||||||
|
|
71
app/src/main/java/org/linphone/utils/ContactUtils.kt
Normal file
71
app/src/main/java/org/linphone/utils/ContactUtils.kt
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2021 Belledonne Communications SARL.
|
||||||
|
*
|
||||||
|
* This file is part of linphone-android
|
||||||
|
* (see https://www.linphone.org).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.linphone.utils
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import java.io.*
|
||||||
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
|
class ContactUtils {
|
||||||
|
companion object {
|
||||||
|
fun getContactVcardFilePath(contactUri: Uri): String? {
|
||||||
|
val contentResolver: ContentResolver = coreContext.context.contentResolver
|
||||||
|
val lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri)
|
||||||
|
Log.i("[Contact Utils] Contact lookup URI is $lookupUri")
|
||||||
|
|
||||||
|
val contactID = FileUtils.getNameFromFilePath(lookupUri.toString())
|
||||||
|
Log.i("[Contact Utils] Contact ID is $contactID")
|
||||||
|
|
||||||
|
val contact = coreContext.contactsManager.findContactById(contactID)
|
||||||
|
if (contact == null) {
|
||||||
|
Log.e("[Contact Utils] Failed to find contact with ID $contactID")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val vcard = contact.friend?.vcard?.asVcard4String()
|
||||||
|
if (vcard == null) {
|
||||||
|
Log.e("[Contact Utils] Failed to get vCard from contact $contactID")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val contactName = contact.fullName?.replace(" ", "_") ?: contactID
|
||||||
|
val vcardPath = FileUtils.getFileStoragePath("$contactName.vcf")
|
||||||
|
val inputStream = ByteArrayInputStream(vcard.toByteArray())
|
||||||
|
try {
|
||||||
|
FileOutputStream(vcardPath).use { out ->
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
var bytesRead: Int
|
||||||
|
while (inputStream.read(buffer).also { bytesRead = it } >= 0) {
|
||||||
|
out.write(buffer, 0, bytesRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("[Contact Utils] creating vcard file exception: $e")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("[Contact Utils] Contact vCard path is $vcardPath")
|
||||||
|
return vcardPath.absolutePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -149,6 +149,95 @@ class FileUtils {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List<String> {
|
||||||
|
var filePath: String? = null
|
||||||
|
if (data != null) {
|
||||||
|
val clipData = data.clipData
|
||||||
|
if (clipData != null && clipData.itemCount > 1) { // Multiple selection
|
||||||
|
Log.i("[File Utils] Found ${clipData.itemCount} elements")
|
||||||
|
val list = arrayListOf<String>()
|
||||||
|
for (i in 0 until clipData.itemCount) {
|
||||||
|
val dataUri = clipData.getItemAt(i).uri
|
||||||
|
if (dataUri != null) {
|
||||||
|
filePath = dataUri.toString()
|
||||||
|
Log.i("[File Utils] Using data URI $filePath")
|
||||||
|
}
|
||||||
|
filePath = cleanFilePath(filePath)
|
||||||
|
if (filePath != null) list.add(filePath)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
} else { // Single selection
|
||||||
|
val dataUri = if (clipData != null && clipData.itemCount == 1) {
|
||||||
|
clipData.getItemAt(0).uri
|
||||||
|
} else {
|
||||||
|
data.data
|
||||||
|
}
|
||||||
|
if (dataUri != null) {
|
||||||
|
filePath = dataUri.toString()
|
||||||
|
Log.i("[File Utils] Using data URI $filePath")
|
||||||
|
} else if (temporaryImageFilePath?.exists() == true) {
|
||||||
|
filePath = temporaryImageFilePath.absolutePath
|
||||||
|
Log.i("[File Utils] Data URI is null, using $filePath")
|
||||||
|
}
|
||||||
|
filePath = cleanFilePath(filePath)
|
||||||
|
if (filePath != null) return arrayListOf(filePath)
|
||||||
|
}
|
||||||
|
} else if (temporaryImageFilePath?.exists() == true) {
|
||||||
|
filePath = temporaryImageFilePath.absolutePath
|
||||||
|
Log.i("[File Utils] Data is null, using $filePath")
|
||||||
|
filePath = cleanFilePath(filePath)
|
||||||
|
if (filePath != null) return arrayListOf(filePath)
|
||||||
|
}
|
||||||
|
return arrayListOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFilePathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): String? {
|
||||||
|
var filePath: String? = null
|
||||||
|
if (data != null) {
|
||||||
|
val clipData = data.clipData
|
||||||
|
if (clipData != null && clipData.itemCount > 1) { // Multiple selection
|
||||||
|
Log.e("[File Utils] Expecting only one file, got ${clipData.itemCount}")
|
||||||
|
} else { // Single selection
|
||||||
|
val dataUri = if (clipData != null && clipData.itemCount == 1) {
|
||||||
|
clipData.getItemAt(0).uri
|
||||||
|
} else {
|
||||||
|
data.data
|
||||||
|
}
|
||||||
|
if (dataUri != null) {
|
||||||
|
filePath = dataUri.toString()
|
||||||
|
Log.i("[File Utils] Using data URI $filePath")
|
||||||
|
} else if (temporaryImageFilePath?.exists() == true) {
|
||||||
|
filePath = temporaryImageFilePath.absolutePath
|
||||||
|
Log.i("[File Utils] Data URI is null, using $filePath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (temporaryImageFilePath?.exists() == true) {
|
||||||
|
filePath = temporaryImageFilePath.absolutePath
|
||||||
|
Log.i("[File Utils] Data is null, using $filePath")
|
||||||
|
}
|
||||||
|
return cleanFilePath(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun cleanFilePath(filePath: String?): String? {
|
||||||
|
if (filePath != null) {
|
||||||
|
val uriToParse = Uri.parse(filePath)
|
||||||
|
if (filePath.startsWith("content://com.android.contacts/contacts/lookup/")) {
|
||||||
|
Log.i("[File Utils] Contact sharing URI detected")
|
||||||
|
return ContactUtils.getContactVcardFilePath(uriToParse)
|
||||||
|
} else if (filePath.startsWith("content://") ||
|
||||||
|
filePath.startsWith("file://")
|
||||||
|
) {
|
||||||
|
val result = getFilePath(coreContext.context, uriToParse)
|
||||||
|
Log.i("[File Utils] Path was using a content or file scheme, real path is: $result")
|
||||||
|
if (result == null) {
|
||||||
|
Log.e("[File Utils] Failed to get access to file $uriToParse")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteFile(filePath: String) {
|
fun deleteFile(filePath: String) {
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
|
|
@ -20,13 +20,10 @@
|
||||||
package org.linphone.utils
|
package org.linphone.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
import android.media.ThumbnailUtils
|
import android.media.ThumbnailUtils
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import java.io.File
|
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
|
||||||
import org.linphone.compatibility.Compatibility
|
import org.linphone.compatibility.Compatibility
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
|
@ -70,92 +67,6 @@ class ImageUtils {
|
||||||
return ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND)
|
return ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List<String> {
|
|
||||||
var imageFilePath: String? = null
|
|
||||||
if (data != null) {
|
|
||||||
val clipData = data.clipData
|
|
||||||
if (clipData != null && clipData.itemCount > 1) { // Multiple selection
|
|
||||||
Log.i("[Image Utils] Found ${clipData.itemCount} elements")
|
|
||||||
val list = arrayListOf<String>()
|
|
||||||
for (i in 0 until clipData.itemCount) {
|
|
||||||
val dataUri = clipData.getItemAt(i).uri
|
|
||||||
if (dataUri != null) {
|
|
||||||
imageFilePath = dataUri.toString()
|
|
||||||
Log.i("[Image Utils] Using data URI $imageFilePath")
|
|
||||||
}
|
|
||||||
imageFilePath = cleanFilePath(imageFilePath)
|
|
||||||
if (imageFilePath != null) list.add(imageFilePath)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
} else { // Single selection
|
|
||||||
val dataUri = if (clipData != null && clipData.itemCount == 1) {
|
|
||||||
clipData.getItemAt(0).uri
|
|
||||||
} else {
|
|
||||||
data.data
|
|
||||||
}
|
|
||||||
if (dataUri != null) {
|
|
||||||
imageFilePath = dataUri.toString()
|
|
||||||
Log.i("[Image Utils] Using data URI $imageFilePath")
|
|
||||||
} else if (temporaryImageFilePath?.exists() == true) {
|
|
||||||
imageFilePath = temporaryImageFilePath.absolutePath
|
|
||||||
Log.i("[Image Utils] Data URI is null, using $imageFilePath")
|
|
||||||
}
|
|
||||||
imageFilePath = cleanFilePath(imageFilePath)
|
|
||||||
if (imageFilePath != null) return arrayListOf(imageFilePath)
|
|
||||||
}
|
|
||||||
} else if (temporaryImageFilePath?.exists() == true) {
|
|
||||||
imageFilePath = temporaryImageFilePath.absolutePath
|
|
||||||
Log.i("[Image Utils] Data is null, using $imageFilePath")
|
|
||||||
imageFilePath = cleanFilePath(imageFilePath)
|
|
||||||
if (imageFilePath != null) return arrayListOf(imageFilePath)
|
|
||||||
}
|
|
||||||
return arrayListOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getFilePathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): String? {
|
|
||||||
var imageFilePath: String? = null
|
|
||||||
if (data != null) {
|
|
||||||
val clipData = data.clipData
|
|
||||||
if (clipData != null && clipData.itemCount > 1) { // Multiple selection
|
|
||||||
Log.e("[Image Utils] Expecting only one file, got ${clipData.itemCount}")
|
|
||||||
} else { // Single selection
|
|
||||||
val dataUri = if (clipData != null && clipData.itemCount == 1) {
|
|
||||||
clipData.getItemAt(0).uri
|
|
||||||
} else {
|
|
||||||
data.data
|
|
||||||
}
|
|
||||||
if (dataUri != null) {
|
|
||||||
imageFilePath = dataUri.toString()
|
|
||||||
Log.i("[Image Utils] Using data URI $imageFilePath")
|
|
||||||
} else if (temporaryImageFilePath?.exists() == true) {
|
|
||||||
imageFilePath = temporaryImageFilePath.absolutePath
|
|
||||||
Log.i("[Image Utils] Data URI is null, using $imageFilePath")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (temporaryImageFilePath?.exists() == true) {
|
|
||||||
imageFilePath = temporaryImageFilePath.absolutePath
|
|
||||||
Log.i("[Image Utils] Data is null, using $imageFilePath")
|
|
||||||
}
|
|
||||||
return cleanFilePath(imageFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun cleanFilePath(filePath: String?): String? {
|
|
||||||
if (filePath != null) {
|
|
||||||
if (filePath.startsWith("content://") ||
|
|
||||||
filePath.startsWith("file://")
|
|
||||||
) {
|
|
||||||
val uriToParse = Uri.parse(filePath)
|
|
||||||
val result = FileUtils.getFilePath(coreContext.context, uriToParse)
|
|
||||||
Log.i("[Image Utils] Path was using a content or file scheme, real path is: $filePath")
|
|
||||||
if (result == null) {
|
|
||||||
Log.e("[Image Utils] Failed to get access to file $uriToParse")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filePath
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRoundBitmap(bitmap: Bitmap): Bitmap? {
|
private fun getRoundBitmap(bitmap: Bitmap): Bitmap? {
|
||||||
val output =
|
val output =
|
||||||
Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
|
Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
|
||||||
|
|
Loading…
Reference in a new issue