Merge pull request #1702 from nextcloud/feature/1661/shareContactFromAttachmentDialog

share contact from attachment dialog
This commit is contained in:
Marcel Hibbe 2021-11-17 14:30:50 +01:00 committed by GitHub
commit 20725387d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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.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<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}"

View file

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

View file

@ -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() {

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: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
android:id="@+id/menu_share_location"
android:layout_width="match_parent"
@ -72,39 +105,6 @@
</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_picture_from_cam"
android:layout_width="match_parent"
@ -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"

View file

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