Browse Source

share contact from attachment dialog

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 3 years ago
parent
commit
4167cb63bf

+ 115 - 47
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())
+                } ?: run {
+                    checkNotNull(intent.data)
+                    intent.data.let {
+                        filesToUpload.add(intent.data.toString())
+                    }
+                }
+                require(filesToUpload.isNotEmpty())
 
-                    val filenamesWithLinebreaks = StringBuilder("\n")
+                val filenamesWithLinebreaks = StringBuilder("\n")
 
-                    for (file in filesToUpload) {
-                        val filename = UriUtils.getFileName(Uri.parse(file), context)
-                        filenamesWithLinebreaks.append(filename).append("\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)
-                        }
+                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)
-                            }
+                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)
-                }
+                    }
+                    .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<out String>, 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<BrowserController.BrowserType>(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}"

+ 2 - 28
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<String> {
         val numbers = mutableListOf<String>()
         val phonesNumbersCursor = context.contentResolver.query(

+ 5 - 0
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() {

+ 34 - 0
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
+    }
+}

+ 10 - 0
app/src/main/res/drawable/ic_baseline_person_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
+</vector>

+ 43 - 10
app/src/main/res/layout/dialog_attachment.xml

@@ -40,7 +40,7 @@
         android:textSize="@dimen/bottom_sheet_text_size" />
 
     <LinearLayout
-        android:id="@+id/menu_share_location"
+        android:id="@+id/menu_attach_contact"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="?android:attr/selectableItemBackground"
@@ -52,20 +52,20 @@
         tools:ignore="UseCompoundDrawables">
 
         <ImageView
-            android:id="@+id/menu_icon_share_location"
+            android:id="@+id/menu_icon_share_contact"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:contentDescription="@null"
-            android:src="@drawable/ic_baseline_location_on_24"
+            android:src="@drawable/ic_baseline_person_24"
             app:tint="@color/colorPrimary" />
 
         <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/txt_share_location"
+            android:id="@+id/shareContactText"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="start|center_vertical"
             android:layout_marginStart="@dimen/standard_margin"
-            android:text="@string/nc_share_location"
+            android:text="@string/nc_share_contact"
             android:textAlignment="viewStart"
             android:textColor="@color/high_emphasis_text"
             android:textSize="@dimen/bottom_sheet_text_size" />
@@ -73,7 +73,7 @@
     </LinearLayout>
 
     <LinearLayout
-        android:id="@+id/menu_attach_file_from_local"
+        android:id="@+id/menu_share_location"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="?android:attr/selectableItemBackground"
@@ -85,20 +85,20 @@
         tools:ignore="UseCompoundDrawables">
 
         <ImageView
-            android:id="@+id/menu_icon_attach_file_from_local"
+            android:id="@+id/menu_icon_share_location"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:contentDescription="@null"
-            android:src="@drawable/upload"
+            android:src="@drawable/ic_baseline_location_on_24"
             app:tint="@color/colorPrimary" />
 
         <androidx.appcompat.widget.AppCompatTextView
-            android:id="@+id/txt_attach_file_from_local"
+            android:id="@+id/txt_share_location"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="start|center_vertical"
             android:layout_marginStart="@dimen/standard_margin"
-            android:text="@string/nc_upload_local_file"
+            android:text="@string/nc_share_location"
             android:textAlignment="viewStart"
             android:textColor="@color/high_emphasis_text"
             android:textSize="@dimen/bottom_sheet_text_size" />
@@ -138,6 +138,39 @@
 
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/menu_attach_file_from_local"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_attach_file_from_local"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/upload"
+            app:tint="@color/colorPrimary" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/txt_attach_file_from_local"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start|center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/nc_upload_local_file"
+            android:textAlignment="viewStart"
+            android:textColor="@color/high_emphasis_text"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+
+    </LinearLayout>
+
     <LinearLayout
         android:id="@+id/menu_attach_file_from_cloud"
         android:layout_width="match_parent"

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -406,6 +406,10 @@
     <string name="nc_location_current_position_description">Your current location</string>
     <string name="nc_location_unknown">Position unknown</string>
 
+    <!-- share contact -->
+    <string name="nc_share_contact">Share contact</string>
+    <string name="nc_share_contact_permission">Permission to read contacts is required</string>
+
     <!-- voice messages -->
     <string name="nc_voice_message_filename">Talk recording from %1$s (%2$s)</string>
     <string name="nc_voice_message_hold_to_record_info">Hold to record, release to send.</string>