mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Properly plumb attachment keyboard in CFv2.
This commit is contained in:
committed by
Greyson Parrelli
parent
36fc9aa82a
commit
cfaef77b21
@@ -5,20 +5,30 @@
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.location.SignalPlace
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityResultContracts.Callbacks
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity
|
||||
import org.thoughtcrime.securesms.maps.PlacePickerActivity
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
@@ -29,16 +39,44 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
* Note, not all activity results will live here but this should handle most of the basic cases. More advance
|
||||
* usages like [AddToContactsContract] can be split out into their own [ActivityResultContract] implementations.
|
||||
*/
|
||||
class ConversationActivityResultContracts(fragment: Fragment, private val callbacks: Callbacks) {
|
||||
class ConversationActivityResultContracts(private val fragment: Fragment, private val callbacks: Callbacks) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationActivityResultContracts::class.java)
|
||||
}
|
||||
|
||||
private val contactShareLauncher = fragment.registerForActivityResult(ContactShareEditor) { contacts -> callbacks.onSendContacts(contacts) }
|
||||
private val selectContactLauncher = fragment.registerForActivityResult(SelectContact) { uri -> callbacks.onContactSelect(uri) }
|
||||
private val mediaSelectionLauncher = fragment.registerForActivityResult(MediaSelection) { result -> callbacks.onMediaSend(result) }
|
||||
private val gifSearchLauncher = fragment.registerForActivityResult(GifSearch) { result -> callbacks.onMediaSend(result) }
|
||||
private val mediaGalleryLauncher = fragment.registerForActivityResult(MediaGallery) { result -> callbacks.onMediaSend(result) }
|
||||
private val selectLocationLauncher = fragment.registerForActivityResult(SelectLocation) { result -> callbacks.onLocationSelected(result?.place, result?.uri) }
|
||||
private val selectFileLauncher = fragment.registerForActivityResult(SelectFile) { result -> callbacks.onFileSelected(result) }
|
||||
|
||||
fun launchContactShareEditor(uri: Uri, chatColors: ChatColors) {
|
||||
contactShareLauncher.launch(uri to chatColors)
|
||||
}
|
||||
|
||||
fun launchSelectContact() {
|
||||
Permissions
|
||||
.with(fragment)
|
||||
.request(Manifest.permission.READ_CONTACTS)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information))
|
||||
.onAllGranted { selectContactLauncher.launch(Unit) }
|
||||
.execute()
|
||||
}
|
||||
|
||||
fun launchGallery(recipientId: RecipientId, text: CharSequence?, isReply: Boolean) {
|
||||
Permissions
|
||||
.with(fragment)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted { mediaGalleryLauncher.launch(MediaSelectionInput(emptyList(), recipientId, text, isReply)) }
|
||||
.execute()
|
||||
}
|
||||
|
||||
fun launchMediaEditor(mediaList: List<Media>, recipientId: RecipientId, text: CharSequence?) {
|
||||
mediaSelectionLauncher.launch(MediaSelectionInput(mediaList, recipientId, text))
|
||||
}
|
||||
@@ -47,6 +85,37 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
|
||||
gifSearchLauncher.launch(GifSearchInput(recipientId, text))
|
||||
}
|
||||
|
||||
fun launchSelectLocation(chatColors: ChatColors) {
|
||||
if (Permissions.hasAny(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)) {
|
||||
selectLocationLauncher.launch(chatColors)
|
||||
} else {
|
||||
Permissions.with(fragment)
|
||||
.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location))
|
||||
.onSomeGranted { selectLocationLauncher.launch(chatColors) }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun launchSelectFile(): Boolean {
|
||||
try {
|
||||
selectFileLauncher.launch(SelectFile.SelectFileMode.DOCUMENT)
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "couldn't complete ACTION_OPEN_DOCUMENT, no activity found. falling back.")
|
||||
}
|
||||
|
||||
try {
|
||||
selectFileLauncher.launch(SelectFile.SelectFileMode.CONTENT)
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "couldn't complete ACTION_GET_CONTENT intent, no activity found. falling back.")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private object MediaSelection : ActivityResultContract<MediaSelectionInput, MediaSendActivityResult?>() {
|
||||
override fun createIntent(context: Context, input: MediaSelectionInput): Intent {
|
||||
val (media, recipientId, text) = input
|
||||
@@ -54,7 +123,26 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult? {
|
||||
return intent?.let { MediaSendActivityResult.fromData(intent) }
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.let { MediaSendActivityResult.fromData(intent) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object MediaGallery : ActivityResultContract<MediaSelectionInput, MediaSendActivityResult?>() {
|
||||
override fun createIntent(context: Context, input: MediaSelectionInput): Intent {
|
||||
val (media, recipientId, text, isReply) = input
|
||||
return MediaSelectionActivity.gallery(context, MessageSendType.SignalMessageSendType, media, recipientId, text, isReply)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult? {
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.let { MediaSendActivityResult.fromData(intent) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +153,25 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): List<Contact> {
|
||||
return intent?.let { IntentCompat.getParcelableArrayListExtra(intent, ContactShareEditActivity.KEY_CONTACTS, Contact::class.java) } ?: emptyList()
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.let { IntentCompat.getParcelableArrayListExtra(intent, ContactShareEditActivity.KEY_CONTACTS, Contact::class.java) } ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SelectContact : ActivityResultContract<Unit, Uri?>() {
|
||||
override fun createIntent(context: Context, input: Unit): Intent {
|
||||
return Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +186,66 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult? {
|
||||
return intent?.let { MediaSendActivityResult.fromData(intent) }
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.let { MediaSendActivityResult.fromData(intent) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class MediaSelectionInput(val media: List<Media>, val recipientId: RecipientId, val text: CharSequence?)
|
||||
private object SelectLocation : ActivityResultContract<ChatColors, SelectLocationOutput?>() {
|
||||
override fun createIntent(context: Context, input: ChatColors): Intent {
|
||||
return Intent(context, PlacePickerActivity::class.java)
|
||||
.putExtra(PlacePickerActivity.KEY_CHAT_COLOR, input.asSingleColor())
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): SelectLocationOutput? {
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.data?.let { uri -> SelectLocationOutput(SignalPlace(PlacePickerActivity.addressFromData(intent)), uri) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SelectFile : ActivityResultContract<SelectFile.SelectFileMode, Uri?>() {
|
||||
override fun createIntent(context: Context, input: SelectFileMode): Intent {
|
||||
return Intent().apply {
|
||||
type = "*/*"
|
||||
|
||||
action = when (input) {
|
||||
SelectFileMode.DOCUMENT -> Intent.ACTION_OPEN_DOCUMENT
|
||||
SelectFileMode.CONTENT -> Intent.ACTION_GET_CONTENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||
return if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
enum class SelectFileMode {
|
||||
DOCUMENT,
|
||||
CONTENT
|
||||
}
|
||||
}
|
||||
|
||||
private data class MediaSelectionInput(val media: List<Media>, val recipientId: RecipientId, val text: CharSequence?, val isReply: Boolean = false)
|
||||
|
||||
private data class GifSearchInput(val recipientId: RecipientId, val text: CharSequence?)
|
||||
|
||||
private data class SelectLocationOutput(val place: SignalPlace, val uri: Uri)
|
||||
|
||||
interface Callbacks {
|
||||
fun onSendContacts(contacts: List<Contact>)
|
||||
fun onMediaSend(result: MediaSendActivityResult?)
|
||||
fun onContactSelect(uri: Uri?)
|
||||
fun onLocationSelected(place: SignalPlace?, uri: Uri?)
|
||||
fun onFileSelected(uri: Uri?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
|
||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.location.SignalPlace
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
|
||||
@@ -2723,6 +2724,28 @@ class ConversationFragment :
|
||||
preUploadResults = result.preUploadResults
|
||||
)
|
||||
}
|
||||
|
||||
override fun onContactSelect(uri: Uri?) {
|
||||
val recipient = viewModel.recipientSnapshot
|
||||
if (uri != null && recipient != null) {
|
||||
conversationActivityResultContracts.launchContactShareEditor(uri, recipient.chatColors)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationSelected(place: SignalPlace?, uri: Uri?) {
|
||||
if (place != null && uri != null) {
|
||||
attachmentManager.setLocation(place, uri)
|
||||
draftViewModel.setLocationDraft(place)
|
||||
} else {
|
||||
Log.w(TAG, "Location missing thumbnail")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFileSelected(uri: Uri?) {
|
||||
if (uri != null) {
|
||||
setMedia(uri, SlideFactory.MediaType.DOCUMENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
@@ -3164,19 +3187,24 @@ class ConversationFragment :
|
||||
private inner class AttachmentKeyboardFragmentListener : FragmentResultListener {
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onFragmentResult(requestKey: String, result: Bundle) {
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
val button: AttachmentKeyboardButton? = result.getSerializable(AttachmentKeyboardFragment.BUTTON_RESULT) as? AttachmentKeyboardButton
|
||||
val media: Media? = result.getParcelable(AttachmentKeyboardFragment.MEDIA_RESULT)
|
||||
|
||||
if (button != null) {
|
||||
when (button) {
|
||||
AttachmentKeyboardButton.GALLERY -> AttachmentManager.selectGallery(this@ConversationFragment, 1, viewModel.recipientSnapshot!!, composeText.textTrimmed, sendButton.selectedSendType, inputPanel.quote.isPresent)
|
||||
AttachmentKeyboardButton.FILE -> AttachmentManager.selectDocument(this@ConversationFragment, 1)
|
||||
AttachmentKeyboardButton.CONTACT -> AttachmentManager.selectContactInfo(this@ConversationFragment, 1)
|
||||
AttachmentKeyboardButton.LOCATION -> AttachmentManager.selectLocation(this@ConversationFragment, 1, viewModel.recipientSnapshot!!.chatColors.asSingleColor())
|
||||
AttachmentKeyboardButton.PAYMENT -> AttachmentManager.selectPayment(this@ConversationFragment, viewModel.recipientSnapshot!!)
|
||||
AttachmentKeyboardButton.GALLERY -> conversationActivityResultContracts.launchGallery(recipient.id, composeText.textTrimmed, inputPanel.quote.isPresent)
|
||||
AttachmentKeyboardButton.CONTACT -> conversationActivityResultContracts.launchSelectContact()
|
||||
AttachmentKeyboardButton.LOCATION -> conversationActivityResultContracts.launchSelectLocation(recipient.chatColors)
|
||||
AttachmentKeyboardButton.PAYMENT -> AttachmentManager.selectPayment(this@ConversationFragment, recipient)
|
||||
AttachmentKeyboardButton.FILE -> {
|
||||
if (!conversationActivityResultContracts.launchSelectFile()) {
|
||||
toast(R.string.AttachmentManager_cant_open_media_selection, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (media != null) {
|
||||
conversationActivityResultContracts.launchMediaEditor(listOf(media), viewModel.recipientSnapshot!!.id, composeText.textTrimmed)
|
||||
conversationActivityResultContracts.launchMediaEditor(listOf(media), recipient.id, composeText.textTrimmed)
|
||||
}
|
||||
|
||||
container.hideInput()
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class PlacePickerActivity extends AppCompatActivity {
|
||||
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator();
|
||||
private static final String KEY_CHAT_COLOR = "chat_color";
|
||||
public static final String KEY_CHAT_COLOR = "chat_color";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user