share contact from attachment dialog

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2021-11-04 15:05:48 +01:00
parent b87cc5927f
commit 4167cb63bf
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
7 changed files with 240 additions and 112 deletions

View File

@ -33,7 +33,9 @@ import android.content.ClipData
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.media.MediaPlayer import android.media.MediaPlayer
@ -46,6 +48,7 @@ import android.os.Handler
import android.os.SystemClock import android.os.SystemClock
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.provider.ContactsContract
import android.text.Editable import android.text.Editable
import android.text.InputFilter import android.text.InputFilter
import android.text.TextUtils import android.text.TextUtils
@ -69,6 +72,7 @@ import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.PermissionChecker import androidx.core.content.PermissionChecker
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.widget.doAfterTextChanged 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.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.image.CloseableImage
import com.google.android.flexbox.FlexboxLayout import com.google.android.flexbox.FlexboxLayout
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.activities.MainActivity 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.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.ConductorRemapping.remapChatController
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.KeyboardUtils 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() { private fun checkReadOnlyState() {
if (currentConversation != null && isAlive()) { if (currentConversation != null && isAlive()) {
if (currentConversation?.shouldShowLobby(conversationUser) ?: false || if (currentConversation?.shouldShowLobby(conversationUser) ?: false ||
@ -1186,8 +1201,12 @@ class ChatController(args: Bundle) :
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { 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 (requestCode == REQUEST_CODE_CHOOSE_FILE) {
if (resultCode == RESULT_OK) {
try { try {
checkNotNull(intent) checkNotNull(intent)
filesToUpload.clear() filesToUpload.clear()
@ -1241,7 +1260,24 @@ class ChatController(args: Bundle) :
.show() .show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) 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) { } else if (requestCode == REQUEST_CODE_PICK_CAMERA) {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
try { 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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) { if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@ -1295,6 +1346,17 @@ class ChatController(args: Bundle) :
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).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) { } else if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "launch cam activity since permission for cam has been 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) { fun showBrowserScreen(browserType: BrowserController.BrowserType) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType)) 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 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 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_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_RECORD_AUDIO_PERMISSION = 222
private const val REQUEST_READ_CONTACT_PERMISSION = 234
private const val REQUEST_CAMERA_PERMISSION = 223 private const val REQUEST_CAMERA_PERMISSION = 223
private const val REQUEST_CODE_PICK_CAMERA: Int = 333 private const val REQUEST_CODE_PICK_CAMERA: Int = 333
private const val OBJECT_MESSAGE: String = "{object}" private const val OBJECT_MESSAGE: String = "{object}"

View File

@ -48,6 +48,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.models.json.search.ContactsByNumberOverall import com.nextcloud.talk.models.json.search.ContactsByNumberOverall
import com.nextcloud.talk.utils.ApiUtils 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.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import io.reactivex.Observer import io.reactivex.Observer
@ -299,7 +300,7 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar
} }
val numbers = getPhoneNumbersFromDeviceContact(id) val numbers = getPhoneNumbersFromDeviceContact(id)
val displayName = getDisplayNameFromDeviceContact(id) val displayName = ContactUtils.getDisplayNameFromDeviceContact(context, id)
if (displayName == null) { if (displayName == null) {
return 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> { private fun getPhoneNumbersFromDeviceContact(id: String?): MutableList<String> {
val numbers = mutableListOf<String>() val numbers = mutableListOf<String>()
val phonesNumbersCursor = context.contentResolver.query( val phonesNumbersCursor = context.contentResolver.query(

View File

@ -79,6 +79,11 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER) chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER)
dismiss() dismiss()
} }
dialogAttachmentBinding.menuAttachContact.setOnClickListener {
chatController.sendChooseContactIntent()
dismiss()
}
} }
override fun onStart() { override fun onStart() {

View File

@ -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
}
}

View File

@ -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>

View File

@ -39,6 +39,39 @@
android:textColor="@color/medium_emphasis_text" android:textColor="@color/medium_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" /> android:textSize="@dimen/bottom_sheet_text_size" />
<LinearLayout
android:id="@+id/menu_attach_contact"
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_share_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_baseline_person_24"
app:tint="@color/colorPrimary" />
<androidx.appcompat.widget.AppCompatTextView
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_contact"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/menu_share_location" android:id="@+id/menu_share_location"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -72,39 +105,6 @@
</LinearLayout> </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 <LinearLayout
android:id="@+id/menu_attach_picture_from_cam" android:id="@+id/menu_attach_picture_from_cam"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -138,6 +138,39 @@
</LinearLayout> </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 <LinearLayout
android:id="@+id/menu_attach_file_from_cloud" android:id="@+id/menu_attach_file_from_cloud"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -406,6 +406,10 @@
<string name="nc_location_current_position_description">Your current location</string> <string name="nc_location_current_position_description">Your current location</string>
<string name="nc_location_unknown">Position unknown</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 --> <!-- voice messages -->
<string name="nc_voice_message_filename">Talk recording from %1$s (%2$s)</string> <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> <string name="nc_voice_message_hold_to_record_info">Hold to record, release to send.</string>