mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Attachments/Share: cleaning code and add contact picking
This commit is contained in:
parent
ee5ebb4b83
commit
0ca8696e88
12 changed files with 153 additions and 53 deletions
|
@ -4,6 +4,7 @@
|
|||
package="im.vector.riotx">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<application
|
||||
android:name=".VectorApplication"
|
||||
|
|
|
@ -54,8 +54,9 @@ const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
|||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
|
||||
|
||||
private const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||
|
||||
// Request code to ask permission to the system (arbitrary values)
|
||||
const val PERMISSION_REQUEST_CODE = 567
|
||||
|
|
|
@ -41,10 +41,17 @@ import com.amulyakhare.textdrawable.TextDrawable
|
|||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.getMeasurements
|
||||
import im.vector.riotx.core.utils.PERMISSIONS_EMPTY
|
||||
import im.vector.riotx.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
|
||||
import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
||||
import kotlin.math.max
|
||||
|
||||
private const val ANIMATION_DURATION = 250
|
||||
|
||||
/**
|
||||
* This class is the view presenting choices for picking attachments.
|
||||
* It will return result through [Callback].
|
||||
*/
|
||||
class AttachmentTypeSelectorView(context: Context,
|
||||
inflater: LayoutInflater,
|
||||
var callback: Callback?)
|
||||
|
@ -74,7 +81,7 @@ class AttachmentTypeSelectorView(context: Context,
|
|||
stickersButton = layout.findViewById<ImageButton>(R.id.attachmentStickersButton).configure(Type.STICKER)
|
||||
audioButton = layout.findViewById<ImageButton>(R.id.attachmentAudioButton).configure(Type.AUDIO)
|
||||
contactButton = layout.findViewById<ImageButton>(R.id.attachmentContactButton).configure(Type.CONTACT)
|
||||
contentView = layout
|
||||
contentView = root
|
||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
height = LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
animationStyle = 0
|
||||
|
@ -197,7 +204,7 @@ class AttachmentTypeSelectorView(context: Context,
|
|||
}
|
||||
|
||||
private fun ImageButton.configure(type: Type): ImageButton {
|
||||
this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type))
|
||||
this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type.ordinal))
|
||||
this.setOnClickListener(TypeClickListener(type))
|
||||
return this
|
||||
}
|
||||
|
@ -211,19 +218,17 @@ class AttachmentTypeSelectorView(context: Context,
|
|||
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
|
||||
CAMERA,
|
||||
GALLERY,
|
||||
FILE,
|
||||
STICKER,
|
||||
AUDIO,
|
||||
CONTACT;
|
||||
|
||||
fun requirePermission(): Boolean {
|
||||
return this != CAMERA && this != STICKER
|
||||
}
|
||||
/**
|
||||
* The all possible types to pick with their required permissions.
|
||||
*/
|
||||
enum class Type(val permissionsBit: Int) {
|
||||
|
||||
CAMERA(PERMISSIONS_EMPTY),
|
||||
GALLERY(PERMISSIONS_FOR_WRITING_FILES),
|
||||
FILE(PERMISSIONS_FOR_WRITING_FILES),
|
||||
STICKER(PERMISSIONS_EMPTY),
|
||||
AUDIO(PERMISSIONS_FOR_WRITING_FILES),
|
||||
CONTACT(PERMISSIONS_FOR_PICKING_CONTACT)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.kbeanie.multipicker.core.PickerManager
|
|||
import com.kbeanie.multipicker.utils.IntentUtils
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.riotx.core.platform.Restorable
|
||||
import timber.log.Timber
|
||||
|
||||
private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY"
|
||||
private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
|
||||
|
@ -45,11 +46,17 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
|
|||
}
|
||||
|
||||
interface Callback {
|
||||
fun onAttachmentsReady(attachments: List<ContentAttachmentData>)
|
||||
fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
|
||||
Timber.v("On contact attachment ready: $contactAttachment")
|
||||
}
|
||||
|
||||
fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>)
|
||||
fun onAttachmentsProcessFailed()
|
||||
}
|
||||
|
||||
// Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
|
||||
private var capturePath: String? = null
|
||||
// The pending type is set if we have to handle permission request. It must be restored if the activity gets killed.
|
||||
var pendingType: AttachmentTypeSelectorView.Type? = null
|
||||
|
||||
private val imagePicker by lazy {
|
||||
|
@ -72,6 +79,10 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
|
|||
pickerManagerFactory.createAudioPicker()
|
||||
}
|
||||
|
||||
private val contactPicker by lazy {
|
||||
pickerManagerFactory.createContactPicker()
|
||||
}
|
||||
|
||||
// Restorable
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -121,6 +132,13 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
|
|||
capturePath = cameraImagePicker.pickImage()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process for handling contact picking
|
||||
*/
|
||||
fun selectContact() {
|
||||
contactPicker.pickContact()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods aims to handle on activity result data.
|
||||
*
|
||||
|
@ -148,6 +166,8 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
|
|||
imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("video")) {
|
||||
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("audio")) {
|
||||
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
|
||||
filePicker.submit(IntentUtils.getPickerIntentForSharing(intent))
|
||||
} else {
|
||||
|
@ -161,6 +181,8 @@ class AttachmentsHelper private constructor(private val pickerManagerFactory: Pi
|
|||
PICK_IMAGE_DEVICE -> imagePicker
|
||||
PICK_IMAGE_CAMERA -> cameraImagePicker
|
||||
PICK_FILE -> filePicker
|
||||
PICK_CONTACT -> contactPicker
|
||||
PICK_AUDIO -> audioPicker
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,18 @@
|
|||
|
||||
package im.vector.riotx.features.attachments
|
||||
|
||||
import com.kbeanie.multipicker.api.entity.ChosenAudio
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
import com.kbeanie.multipicker.api.entity.*
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
|
||||
fun ChosenContact.toContactAttachment(): ContactAttachment {
|
||||
return ContactAttachment(
|
||||
displayName = displayName,
|
||||
photoUri = photoUri,
|
||||
emails = emails.toList(),
|
||||
phones = phones.toList()
|
||||
)
|
||||
}
|
||||
|
||||
fun ChosenFile.toContentAttachmentData(): ContentAttachmentData {
|
||||
return ContentAttachmentData(
|
||||
path = originalPath,
|
||||
|
@ -61,8 +67,8 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData {
|
|||
type = mapType(),
|
||||
name = displayName,
|
||||
size = size,
|
||||
height = height?.toLong(),
|
||||
width = width?.toLong(),
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
date = createdAt?.time ?: System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
|
@ -74,8 +80,8 @@ fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData {
|
|||
type = ContentAttachmentData.Type.VIDEO,
|
||||
size = size,
|
||||
date = createdAt?.time ?: System.currentTimeMillis(),
|
||||
height = height?.toLong(),
|
||||
width = width?.toLong(),
|
||||
height = height.toLong(),
|
||||
width = width.toLong(),
|
||||
duration = duration,
|
||||
name = displayName
|
||||
)
|
||||
|
|
|
@ -21,15 +21,22 @@ import com.kbeanie.multipicker.api.callbacks.ContactPickerCallback
|
|||
import com.kbeanie.multipicker.api.callbacks.FilePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback
|
||||
import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback
|
||||
import com.kbeanie.multipicker.api.entity.ChosenAudio
|
||||
import com.kbeanie.multipicker.api.entity.ChosenFile
|
||||
import com.kbeanie.multipicker.api.entity.ChosenImage
|
||||
import com.kbeanie.multipicker.api.entity.ChosenVideo
|
||||
import com.kbeanie.multipicker.api.entity.*
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback]
|
||||
*/
|
||||
class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback) : ImagePickerCallback, FilePickerCallback, VideoPickerCallback, AudioPickerCallback {
|
||||
class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback) : ImagePickerCallback, FilePickerCallback, VideoPickerCallback, AudioPickerCallback, ContactPickerCallback {
|
||||
|
||||
override fun onContactChosen(contact: ChosenContact?) {
|
||||
if (contact == null) {
|
||||
callback.onAttachmentsProcessFailed()
|
||||
} else {
|
||||
val contactAttachment = contact.toContactAttachment()
|
||||
callback.onContactAttachmentReady(contactAttachment)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAudiosChosen(audios: MutableList<ChosenAudio>?) {
|
||||
if (audios.isNullOrEmpty()) {
|
||||
|
@ -38,7 +45,7 @@ class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback
|
|||
val attachments = audios.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +56,7 @@ class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback
|
|||
val attachments = files.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +67,7 @@ class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback
|
|||
val attachments = images.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +78,7 @@ class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback
|
|||
val attachments = videos.map {
|
||||
it.toContentAttachmentData()
|
||||
}
|
||||
callback.onAttachmentsReady(attachments)
|
||||
callback.onContentAttachmentsReady(attachments)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package im.vector.riotx.features.attachments
|
||||
|
||||
/**
|
||||
* Data class holding values of a picked contact
|
||||
* Can be send as a text message waiting for the protocol to handle contact.
|
||||
*/
|
||||
data class ContactAttachment(
|
||||
val displayName: String,
|
||||
val photoUri: String?,
|
||||
val phones: List<String> = emptyList(),
|
||||
val emails: List<String> = emptyList()
|
||||
) {
|
||||
|
||||
fun toHumanReadable(): String {
|
||||
val stringBuilder = StringBuilder(displayName)
|
||||
phones.concatIn(stringBuilder)
|
||||
emails.concatIn(stringBuilder)
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
private fun List<String>.concatIn(stringBuilder: StringBuilder) {
|
||||
if (isNotEmpty()) {
|
||||
stringBuilder.append("\n")
|
||||
for (i in 0 until size - 1) {
|
||||
val value = get(i)
|
||||
stringBuilder.append(value).append("\n")
|
||||
}
|
||||
stringBuilder.append(last())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,9 @@ import com.kbeanie.multipicker.api.FilePicker
|
|||
import com.kbeanie.multipicker.api.ImagePicker
|
||||
import com.kbeanie.multipicker.api.VideoPicker
|
||||
|
||||
/**
|
||||
* Factory for creating different pickers. It allows to use with fragment or activity builders.
|
||||
*/
|
||||
interface PickerManagerFactory {
|
||||
|
||||
fun createImagePicker(): ImagePicker
|
||||
|
@ -37,6 +40,8 @@ interface PickerManagerFactory {
|
|||
|
||||
fun createAudioPicker(): AudioPicker
|
||||
|
||||
fun createContactPicker(): ContactPicker
|
||||
|
||||
}
|
||||
|
||||
class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
|
||||
|
@ -76,6 +81,12 @@ class ActivityPickerManagerFactory(private val activity: Activity, callback: Att
|
|||
it.setAudioPickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createContactPicker(): ContactPicker {
|
||||
return ContactPicker(activity).also {
|
||||
it.setContactPickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory {
|
||||
|
@ -116,5 +127,11 @@ class FragmentPickerManagerFactory(private val fragment: Fragment, callback: Att
|
|||
}
|
||||
}
|
||||
|
||||
override fun createContactPicker(): ContactPicker {
|
||||
return ContactPicker(fragment).also {
|
||||
it.setContactPickerCallback(attachmentsPickerCallback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ import im.vector.riotx.core.utils.Debouncer
|
|||
import im.vector.riotx.core.utils.createUIHandler
|
||||
import im.vector.riotx.features.attachments.AttachmentTypeSelectorView
|
||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||
import im.vector.riotx.features.attachments.ContactAttachment
|
||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||
import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||
|
@ -1120,7 +1121,7 @@ class RoomDetailFragment :
|
|||
// AttachmentTypeSelectorView.Callback
|
||||
|
||||
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
|
||||
if (!type.requirePermission() || checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) {
|
||||
if (checkPermissions(type.permissionsBit, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) {
|
||||
launchAttachmentProcess(type)
|
||||
} else {
|
||||
attachmentsHelper.pendingType = type
|
||||
|
@ -1133,19 +1134,23 @@ class RoomDetailFragment :
|
|||
AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile()
|
||||
AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery()
|
||||
AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio()
|
||||
AttachmentTypeSelectorView.Type.CONTACT -> vectorBaseActivity.notImplemented("Picking contacts")
|
||||
AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact()
|
||||
AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers")
|
||||
}
|
||||
}
|
||||
|
||||
// AttachmentsHelper.Callback
|
||||
|
||||
override fun onAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
Timber.v("onAttachmentsReady")
|
||||
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMedia(attachments))
|
||||
}
|
||||
|
||||
override fun onAttachmentsProcessFailed() {
|
||||
Timber.v("onAttachmentsProcessFailed")
|
||||
Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
|
||||
val formattedContact = contactAttachment.toHumanReadable()
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(formattedContact, false))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package im.vector.riotx.features.share
|
|||
|
||||
import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
|
@ -12,12 +11,10 @@ import im.vector.riotx.core.di.ScreenComponent
|
|||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.attachments.AttachmentsHelper
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListParams
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import im.vector.riotx.features.login.LoginConfig
|
||||
import kotlinx.android.synthetic.main.activity_incoming_share.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -25,7 +22,6 @@ import javax.inject.Inject
|
|||
class IncomingShareActivity :
|
||||
VectorBaseActivity(), AttachmentsHelper.Callback {
|
||||
|
||||
|
||||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||
private lateinit var roomListFragment: RoomListFragment
|
||||
private lateinit var attachmentsHelper: AttachmentsHelper
|
||||
|
@ -40,6 +36,8 @@ class IncomingShareActivity :
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// If we are not logged in, stop the sharing process and open login screen.
|
||||
// In the future, we might want to relaunch the sharing process after login.
|
||||
if (!sessionHolder.hasActiveSession()) {
|
||||
startLoginActivity()
|
||||
return
|
||||
|
@ -63,7 +61,7 @@ class IncomingShareActivity :
|
|||
}
|
||||
}
|
||||
|
||||
override fun onAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
val roomListParams = RoomListParams(RoomListFragment.DisplayMode.SHARE, sharedData = SharedData.Attachments(attachments))
|
||||
roomListFragment = RoomListFragment.newInstance(roomListParams)
|
||||
replaceFragment(roomListFragment, R.id.shareRoomListFragmentContainer)
|
||||
|
@ -74,7 +72,7 @@ class IncomingShareActivity :
|
|||
}
|
||||
|
||||
private fun cantManageShare() {
|
||||
Toast.makeText(this, "Couldn't handle share data", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, R.string.error_handling_incoming_share, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
|
@ -93,9 +91,6 @@ class IncomingShareActivity :
|
|||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the login screen with identity server and home server pre-filled
|
||||
*/
|
||||
private fun startLoginActivity() {
|
||||
val intent = LoginActivity.newIntent(this, null)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="Camera" />
|
||||
android:text="@string/attachment_type_camera" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="Gallery" />
|
||||
android:text="@string/attachment_type_gallery" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -74,7 +74,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="File" />
|
||||
android:text="@string/attachment_type_file" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -103,7 +103,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="Audio" />
|
||||
android:text="@string/attachment_type_audio" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -123,7 +123,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="Contact" />
|
||||
android:text="@string/attachment_type_contact" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -143,7 +143,7 @@
|
|||
|
||||
<TextView
|
||||
style="@style/AttachmentTypeSelectorLabel"
|
||||
android:text="Stickers" />
|
||||
android:text="@string/attachment_type_sticker" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -37,4 +37,13 @@
|
|||
|
||||
<string name="error_file_too_big">"The file '%1$s' (%2$s) is too large to upload. The limit is %3$s."</string>
|
||||
|
||||
<string name="error_attachment">"An error occurred while retrieving the attachment."</string>
|
||||
<string name="attachment_type_file">"File"</string>
|
||||
<string name="attachment_type_contact">"Contact"</string>
|
||||
<string name="attachment_type_camera">"Camera"</string>
|
||||
<string name="attachment_type_audio">"Audio"</string>
|
||||
<string name="attachment_type_gallery">"Gallery"</string>
|
||||
<string name="attachment_type_sticker">"Sticker"</string>
|
||||
<string name="error_handling_incoming_share">Couldn\'t handle share data</string>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue