From 4167cb63bf04fdc56bfd4e279614dbe4a83ff040 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 4 Nov 2021 15:05:48 +0100 Subject: [PATCH] share contact from attachment dialog Signed-off-by: Marcel Hibbe --- .../talk/controllers/ChatController.kt | 170 ++++++++++++------ .../talk/jobs/ContactAddressBookWorker.kt | 30 +--- .../talk/ui/dialog/AttachmentDialog.kt | 5 + .../com/nextcloud/talk/utils/ContactUtils.kt | 34 ++++ .../res/drawable/ic_baseline_person_24.xml | 10 ++ app/src/main/res/layout/dialog_attachment.xml | 99 ++++++---- app/src/main/res/values/strings.xml | 4 + 7 files changed, 240 insertions(+), 112 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/utils/ContactUtils.kt create mode 100644 app/src/main/res/drawable/ic_baseline_person_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 4cae00a45..3d066ab9c 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -33,7 +33,9 @@ import android.content.ClipData import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.res.AssetFileDescriptor import android.content.res.Resources +import android.database.Cursor import android.graphics.Bitmap import android.graphics.drawable.ColorDrawable import android.media.MediaPlayer @@ -46,6 +48,7 @@ import android.os.Handler import android.os.SystemClock import android.os.VibrationEffect import android.os.Vibrator +import android.provider.ContactsContract import android.text.Editable import android.text.InputFilter import android.text.TextUtils @@ -69,6 +72,7 @@ import android.widget.RelativeLayout import android.widget.Toast import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider import androidx.core.content.PermissionChecker import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.widget.doAfterTextChanged @@ -93,6 +97,7 @@ import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber import com.facebook.imagepipeline.image.CloseableImage import com.google.android.flexbox.FlexboxLayout +import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.MainActivity @@ -140,6 +145,7 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping.remapChatController +import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.KeyboardUtils @@ -1100,6 +1106,15 @@ class ChatController(args: Bundle) : ) } + private fun requestReadContacts() { + requestPermissions( + arrayOf( + Manifest.permission.READ_CONTACTS + ), + REQUEST_READ_CONTACT_PERMISSION + ) + } + private fun checkReadOnlyState() { if (currentConversation != null && isAlive()) { if (currentConversation?.shouldShowLobby(conversationUser) ?: false || @@ -1186,62 +1201,83 @@ class ChatController(args: Bundle) : } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { + if (resultCode != RESULT_OK) { + Log.e(TAG, "resultCode for received intent was != ok") + return + } + if (requestCode == REQUEST_CODE_CHOOSE_FILE) { - if (resultCode == RESULT_OK) { - try { - checkNotNull(intent) - filesToUpload.clear() - intent.clipData?.let { - for (index in 0 until it.itemCount) { - filesToUpload.add(it.getItemAt(index).uri.toString()) - } - } ?: run { - checkNotNull(intent.data) - intent.data.let { - filesToUpload.add(intent.data.toString()) - } + try { + checkNotNull(intent) + filesToUpload.clear() + intent.clipData?.let { + for (index in 0 until it.itemCount) { + filesToUpload.add(it.getItemAt(index).uri.toString()) } - require(filesToUpload.isNotEmpty()) - - val filenamesWithLinebreaks = StringBuilder("\n") - - for (file in filesToUpload) { - val filename = UriUtils.getFileName(Uri.parse(file), context) - filenamesWithLinebreaks.append(filename).append("\n") + } ?: run { + checkNotNull(intent.data) + intent.data.let { + filesToUpload.add(intent.data.toString()) } - - val confirmationQuestion = when (filesToUpload.size) { - 1 -> context?.resources?.getString(R.string.nc_upload_confirm_send_single)?.let { - String.format(it, title) - } - else -> context?.resources?.getString(R.string.nc_upload_confirm_send_multiple)?.let { - String.format(it, title) - } - } - - LovelyStandardDialog(activity) - .setPositiveButtonColorRes(R.color.nc_darkGreen) - .setTitle(confirmationQuestion) - .setMessage(filenamesWithLinebreaks.toString()) - .setPositiveButton(R.string.nc_yes) { v -> - if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) { - uploadFiles(filesToUpload, false) - } else { - UploadAndShareFilesWorker.requestStoragePermission(this) - } - } - .setNegativeButton(R.string.nc_no) {} - .show() - } catch (e: IllegalStateException) { - Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) - .show() - Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) - } catch (e: IllegalArgumentException) { - Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) - .show() - Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) } + require(filesToUpload.isNotEmpty()) + + val filenamesWithLinebreaks = StringBuilder("\n") + + for (file in filesToUpload) { + val filename = UriUtils.getFileName(Uri.parse(file), context) + filenamesWithLinebreaks.append(filename).append("\n") + } + + val confirmationQuestion = when (filesToUpload.size) { + 1 -> context?.resources?.getString(R.string.nc_upload_confirm_send_single)?.let { + String.format(it, title) + } + else -> context?.resources?.getString(R.string.nc_upload_confirm_send_multiple)?.let { + String.format(it, title) + } + } + + LovelyStandardDialog(activity) + .setPositiveButtonColorRes(R.color.nc_darkGreen) + .setTitle(confirmationQuestion) + .setMessage(filenamesWithLinebreaks.toString()) + .setPositiveButton(R.string.nc_yes) { v -> + if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) { + uploadFiles(filesToUpload, false) + } else { + UploadAndShareFilesWorker.requestStoragePermission(this) + } + } + .setNegativeButton(R.string.nc_no) {} + .show() + } catch (e: IllegalStateException) { + Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) + .show() + Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) + } catch (e: IllegalArgumentException) { + Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) + .show() + Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) } + } else if (requestCode == REQUEST_CODE_SELECT_CONTACT) { + val contactUri = intent?.data ?: return + val cursor: Cursor? = activity?.contentResolver!!.query(contactUri, null, null, null, null) + + if (cursor != null && cursor.moveToFirst()) { + val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)) + val fileName = ContactUtils.getDisplayNameFromDeviceContact(context!!, id) + ".vcf" + val file = File(context?.cacheDir, fileName) + writeContactToVcfFile(cursor, file) + + val shareUri = FileProvider.getUriForFile( + activity!!, + BuildConfig.APPLICATION_ID, + File(file.absolutePath) + ) + uploadFiles(mutableListOf(shareUri.toString()), false) + } + cursor?.close() } else if (requestCode == REQUEST_CODE_PICK_CAMERA) { if (resultCode == RESULT_OK) { try { @@ -1273,6 +1309,21 @@ class ChatController(args: Bundle) : } } + private fun writeContactToVcfFile(cursor: Cursor, file: File) { + val lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)) + val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey) + + val fd: AssetFileDescriptor = activity?.contentResolver!!.openAssetFileDescriptor(uri, "r")!! + val fis = fd.createInputStream() + + file.createNewFile() + fis.use { input -> + file.outputStream().use { output -> + input.copyTo(output) + } + } + } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -1295,6 +1346,17 @@ class ChatController(args: Bundle) : Toast.LENGTH_LONG ).show() } + } else if (requestCode == REQUEST_READ_CONTACT_PERMISSION) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) + startActivityForResult(intent, REQUEST_CODE_SELECT_CONTACT) + } else { + Toast.makeText( + context, + context!!.getString(R.string.nc_share_contact_permission), + Toast.LENGTH_LONG + ).show() + } } else if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "launch cam activity since permission for cam has been granted") @@ -1358,6 +1420,10 @@ class ChatController(args: Bundle) : ) } + fun sendChooseContactIntent() { + requestReadContacts() + } + fun showBrowserScreen(browserType: BrowserController.BrowserType) { val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap(browserType)) @@ -2621,7 +2687,9 @@ class ChatController(args: Bundle) : private const val MESSAGE_MAX_LENGTH: Int = 1000 private const val AGE_THREHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000) private const val REQUEST_CODE_CHOOSE_FILE: Int = 555 + private const val REQUEST_CODE_SELECT_CONTACT: Int = 666 private const val REQUEST_RECORD_AUDIO_PERMISSION = 222 + private const val REQUEST_READ_CONTACT_PERMISSION = 234 private const val REQUEST_CAMERA_PERMISSION = 223 private const val REQUEST_CODE_PICK_CAMERA: Int = 333 private const val OBJECT_MESSAGE: String = "{object}" diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt index 474dabaab..7ac98967f 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt @@ -48,6 +48,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.models.json.search.ContactsByNumberOverall import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.ContactUtils import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences import io.reactivex.Observer @@ -299,7 +300,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar } val numbers = getPhoneNumbersFromDeviceContact(id) - val displayName = getDisplayNameFromDeviceContact(id) + val displayName = ContactUtils.getDisplayNameFromDeviceContact(context, id) if (displayName == null) { return @@ -393,33 +394,6 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar } } - private fun getDisplayNameFromDeviceContact(id: String?): String? { - var displayName: String? = null - val whereName = - ContactsContract.Data.MIMETYPE + - " = ? AND " + - ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + - " = ?" - val whereNameParams = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id) - val nameCursor = context.contentResolver.query( - ContactsContract.Data.CONTENT_URI, - null, - whereName, - whereNameParams, - ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME - ) - if (nameCursor != null) { - while (nameCursor.moveToNext()) { - displayName = - nameCursor.getString( - nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME) - ) - } - nameCursor.close() - } - return displayName - } - private fun getPhoneNumbersFromDeviceContact(id: String?): MutableList { val numbers = mutableListOf() val phonesNumbersCursor = context.contentResolver.query( diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt index c34ff8bb1..7e07dac79 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt @@ -79,6 +79,11 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER) dismiss() } + + dialogAttachmentBinding.menuAttachContact.setOnClickListener { + chatController.sendChooseContactIntent() + dismiss() + } } override fun onStart() { diff --git a/app/src/main/java/com/nextcloud/talk/utils/ContactUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ContactUtils.kt new file mode 100644 index 000000000..3f65dfe0e --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/ContactUtils.kt @@ -0,0 +1,34 @@ +package com.nextcloud.talk.utils + +import android.content.Context +import android.provider.ContactsContract + +object ContactUtils { + + fun getDisplayNameFromDeviceContact(context: Context, id: String?): String? { + var displayName: String? = null + val whereName = + ContactsContract.Data.MIMETYPE + + " = ? AND " + + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + + " = ?" + val whereNameParams = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id) + val nameCursor = context.contentResolver.query( + ContactsContract.Data.CONTENT_URI, + null, + whereName, + whereNameParams, + ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME + ) + if (nameCursor != null) { + while (nameCursor.moveToNext()) { + displayName = + nameCursor.getString( + nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME) + ) + } + nameCursor.close() + } + return displayName + } +} diff --git a/app/src/main/res/drawable/ic_baseline_person_24.xml b/app/src/main/res/drawable/ic_baseline_person_24.xml new file mode 100644 index 000000000..6bdced2dc --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_person_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_attachment.xml b/app/src/main/res/layout/dialog_attachment.xml index dbc3332ba..502d92456 100644 --- a/app/src/main/res/layout/dialog_attachment.xml +++ b/app/src/main/res/layout/dialog_attachment.xml @@ -39,6 +39,39 @@ android:textColor="@color/medium_emphasis_text" android:textSize="@dimen/bottom_sheet_text_size" /> + + + + + + + + - - - - - - - - + + + + + + + + Your current location Position unknown + + Share contact + Permission to read contacts is required + Talk recording from %1$s (%2$s) Hold to record, release to send.