Merged PR 218498: PeoplePicker: Add support for Drag and Drop

- New flag for enabling drag and drop for `PeoplePickerTextView`s
- Drag events should maintain all relevant data for the `Persona` object so that the token can be recreated on drop. In cases where it's not clear we have a `Persona` object, I kept Outlook's `getObjectForClipDataOnDrop` method that recreates such an object if possible. You can test how this fallback works by commenting this out in `swallowPersonaChipDrop`:
```
if (localState != null && localState is IPersona)
    persona = localState
```
- Fixed a bug where `avatarImageBitmap` and `avatarImageUri` were not loading for the `PersonaChipView`s
- Added emails to the example to better track data moving during drag and drop
- I left out the `View.OnDragListener` for now because it didn't seem essential - we are using `onDragEvent` instead. However, we could open this api up (can read about it [here](https://developer.android.com/guide/topics/ui/drag-drop)). If we did expose that api, I think we'd need a way to enforce that it returns false to maintain drag and drop functionality. Outlook uses the listener tell their cc and bcc fields to expand when a drag from the to field starts.
- To test Outlook's drag and drop, comment out the check for `Feature.COMPOSE_CONTACTS_DRAG_AND_DROP` on line 1507 in `ComposeActivity`.

Related work items: #671303
This commit is contained in:
Emily Lynam 2019-01-10 23:13:38 +00:00
Родитель 37fb4f5950
Коммит ea43301431
8 изменённых файлов: 349 добавлений и 60 удалений

Просмотреть файл

@ -4,8 +4,8 @@
package com.microsoft.officeuifabricdemo.demos
import android.app.AlertDialog
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.view.ViewGroup
import android.widget.LinearLayout
import com.microsoft.officeuifabric.peoplepicker.PeoplePickerPersonaChipClickStyle
@ -13,7 +13,9 @@ import com.microsoft.officeuifabric.peoplepicker.PeoplePickerView
import com.microsoft.officeuifabric.persona.IPersona
import com.microsoft.officeuifabricdemo.DemoActivity
import com.microsoft.officeuifabricdemo.R
import com.microsoft.officeuifabricdemo.util.createCustomPersona
import com.microsoft.officeuifabricdemo.util.createPersonaList
import kotlinx.android.synthetic.main.activity_demo_detail.*
import kotlinx.android.synthetic.main.activity_people_picker_view.*
import java.util.*
import kotlin.collections.ArrayList
@ -47,10 +49,15 @@ class PeoplePickerViewActivity : DemoActivity() {
people_picker_select.pickedPersonas = selectPickedPersonas
people_picker_select.showSearchDirectoryButton = true
people_picker_select.searchDirectorySuggestionsListener = createPersonaSuggestionsListener(selectSearchDirectoryPersonas)
people_picker_select.allowPersonaChipDragAndDrop = true
people_picker_select.onCreatePersona = { name, email ->
createCustomPersona(this, name, email)
}
people_picker_select_deselect.availablePersonas = samplePersonas
val selectDeselectPickedPersonas = arrayListOf(samplePersonas[2])
people_picker_select_deselect.pickedPersonas = selectDeselectPickedPersonas
people_picker_select_deselect.allowPersonaChipDragAndDrop = true
// Use code to set personaChipClickStyle and label
@ -90,6 +97,7 @@ class PeoplePickerViewActivity : DemoActivity() {
this.personaChipClickStyle = personaChipClickStyle
this.personaSuggestionsListener = personaSuggestionsListener
this.pickedPersonasChangeListener = pickedPersonasChangeListener
allowPersonaChipDragAndDrop = true
}
people_picker_layout.addView(peoplePickerView)
}
@ -97,22 +105,15 @@ class PeoplePickerViewActivity : DemoActivity() {
private fun createPickedPersonasChangeListener(): PeoplePickerView.PickedPersonasChangeListener {
return object : PeoplePickerView.PickedPersonasChangeListener {
override fun onPersonaAdded(persona: IPersona) {
showPickedPersonaDialog(getString(R.string.people_picker_dialog_title_added), persona)
showSnackbar("${getString(R.string.people_picker_dialog_title_added)} ${if (!persona.name.isEmpty()) persona.name else persona.email}")
}
override fun onPersonaRemoved(persona: IPersona) {
showPickedPersonaDialog(getString(R.string.people_picker_dialog_title_removed), persona)
showSnackbar("${getString(R.string.people_picker_dialog_title_removed)} ${if (!persona.name.isEmpty()) persona.name else persona.email}")
}
}
}
private fun showPickedPersonaDialog(title: String, persona: IPersona) {
val dialog = AlertDialog.Builder(this)
dialog.setTitle(title)
dialog.setMessage(if (!persona.name.isEmpty()) persona.name else persona.email)
dialog.show()
}
private fun createPersonaSuggestionsListener(personas: ArrayList<IPersona>): PeoplePickerView.PersonaSuggestionsListener {
return object : PeoplePickerView.PersonaSuggestionsListener {
override fun onGetSuggestedPersonas(
@ -134,6 +135,10 @@ class PeoplePickerViewActivity : DemoActivity() {
}
}
private fun showSnackbar(text: String) {
Snackbar.make(root_view, text, Snackbar.LENGTH_SHORT).show()
}
// Basic custom filtering example
private fun filterPersonas(
searchConstraint: CharSequence?,

Просмотреть файл

@ -14,6 +14,7 @@ import android.support.v4.content.ContextCompat
import com.microsoft.officeuifabric.persona.IPersona
import com.microsoft.officeuifabric.persona.Persona
import com.microsoft.officeuifabricdemo.R
import java.io.Serializable
fun createPersonaList(context: Context?): ArrayList<IPersona> {
val context = context ?: return arrayListOf()
@ -21,25 +22,30 @@ fun createPersonaList(context: Context?): ArrayList<IPersona> {
createPersona(
context.getString(R.string.persona_name_amanda_brady),
context.getString(R.string.persona_subtitle_manager),
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_amanda_brady)
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_amanda_brady),
email = context.getString(R.string.persona_email_amanda_brady)
),
createPersona(
context.getString(R.string.persona_name_lydia_bauer),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_lydia_bauer)
),
createPersona(
context.getString(R.string.persona_name_daisy_phillips),
context.getString(R.string.persona_subtitle_designer),
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_daisy_phillips)
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_daisy_phillips),
email = context.getString(R.string.persona_email_daisy_phillips)
),
createPersona(
context.getString(R.string.persona_name_allan_munger) + context.getString(R.string.persona_truncation),
context.getString(R.string.persona_subtitle_manager)
context.getString(R.string.persona_subtitle_manager),
email = context.getString(R.string.persona_email_allan_munger)
),
createPersona(
context.getString(R.string.persona_name_mauricio_august),
context.getString(R.string.persona_name_kat_larsson),
context.getString(R.string.persona_subtitle_designer),
imageBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.avatar_mauricio_august)
imageBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.avatar_kat_larsson),
email = context.getString(R.string.persona_email_kat_larsson)
),
createPersona(
context.getString(R.string.persona_name_ashley_mccarthy),
@ -48,92 +54,114 @@ fun createPersonaList(context: Context?): ArrayList<IPersona> {
createPersona(
context.getString(R.string.persona_name_miguel_garcia),
context.getString(R.string.persona_subtitle_researcher),
imageUri = getUriFromResource(context, R.drawable.avatar_miguel_garcia)
imageUri = getUriFromResource(context, R.drawable.avatar_miguel_garcia),
email = context.getString(R.string.persona_email_miguel_garcia)
),
createPersona(
context.getString(R.string.persona_name_carole_poland),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_carole_poland)
),
createPersona(
context.getString(R.string.persona_name_mona_kane),
context.getString(R.string.persona_subtitle_designer)
context.getString(R.string.persona_subtitle_designer),
email = context.getString(R.string.persona_email_mona_kane)
),
createPersona(
context.getString(R.string.persona_name_carlos_slattery),
context.getString(R.string.persona_subtitle_engineer)
context.getString(R.string.persona_subtitle_engineer),
email = context.getString(R.string.persona_email_carlos_slattery)
),
createPersona(
context.getString(R.string.persona_name_wanda_howard),
context.getString(R.string.persona_subtitle_engineer)
context.getString(R.string.persona_subtitle_engineer),
email = context.getString(R.string.persona_email_wanda_howard)
),
createPersona(
context.getString(R.string.persona_name_tim_deboer),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_tim_deboer)
),
createPersona(
context.getString(R.string.persona_name_robin_counts),
context.getString(R.string.persona_subtitle_designer)
context.getString(R.string.persona_subtitle_designer),
email = context.getString(R.string.persona_email_robin_counts)
),
createPersona(
context.getString(R.string.persona_name_elliot_woordward),
context.getString(R.string.persona_subtitle_designer)
context.getString(R.string.persona_name_elliot_woodward),
context.getString(R.string.persona_subtitle_designer),
email = context.getString(R.string.persona_email_elliot_woodward)
),
createPersona(
context.getString(R.string.persona_name_cecil_folk),
context.getString(R.string.persona_subtitle_manager)
context.getString(R.string.persona_subtitle_manager),
email = context.getString(R.string.persona_email_cecil_folk)
),
createPersona(
context.getString(R.string.persona_name_celeste_burton),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_celeste_burton)
),
createPersona(
context.getString(R.string.persona_name_elvia_atkins),
context.getString(R.string.persona_subtitle_designer),
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_elvia_atkins)
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_elvia_atkins),
email = context.getString(R.string.persona_email_elvia_atkins)
),
createPersona(
context.getString(R.string.persona_name_colin_ballinger),
context.getString(R.string.persona_subtitle_manager),
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_colin_ballinger)
imageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_colin_ballinger),
email = context.getString(R.string.persona_email_colin_ballinger)
),
createPersona(
context.getString(R.string.persona_name_katri_ahokas),
context.getString(R.string.persona_subtitle_designer),
imageBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.avatar_katri_ahokas)
imageBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.avatar_katri_ahokas),
email = context.getString(R.string.persona_email_katri_ahokas)
),
createPersona(
context.getString(R.string.persona_name_henry_brill),
context.getString(R.string.persona_subtitle_engineer)
context.getString(R.string.persona_subtitle_engineer),
email = context.getString(R.string.persona_email_henry_brill)
),
createPersona(
context.getString(R.string.persona_name_johnie_mcconnell),
context.getString(R.string.persona_subtitle_researcher),
imageUri = getUriFromResource(context, R.drawable.avatar_johnie_mcconnell)
imageUri = getUriFromResource(context, R.drawable.avatar_johnie_mcconnell),
email = context.getString(R.string.persona_email_johnie_mcconnell)
),
createPersona(
context.getString(R.string.persona_name_kevin_sturgis),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_kevin_sturgis)
),
createPersona(
context.getString(R.string.persona_name_kristen_patterson),
context.getString(R.string.persona_subtitle_designer)
context.getString(R.string.persona_subtitle_designer),
email = context.getString(R.string.persona_email_kristen_patterson)
),
createPersona(
context.getString(R.string.persona_name_charlotte_waltson),
context.getString(R.string.persona_subtitle_engineer)
context.getString(R.string.persona_subtitle_engineer),
email = context.getString(R.string.persona_email_charlotte_waltson)
),
createPersona(
context.getString(R.string.persona_name_erik_nason),
context.getString(R.string.persona_subtitle_engineer)
context.getString(R.string.persona_subtitle_engineer),
email = context.getString(R.string.persona_email_erik_nason)
),
createPersona(
context.getString(R.string.persona_name_isaac_fielder),
context.getString(R.string.persona_subtitle_researcher)
context.getString(R.string.persona_subtitle_researcher),
email = context.getString(R.string.persona_email_isaac_fielder)
),
createPersona(
context.getString(R.string.persona_name_mauricio_august),
context.getString(R.string.persona_subtitle_designer)
)
context.getString(R.string.persona_subtitle_designer),
email = context.getString(R.string.persona_email_mauricio_august)
),
createCustomPersona(context)
)
}
@ -151,13 +179,36 @@ private fun createPersona(
imageResource: Int? = null,
imageDrawable: Drawable? = null,
imageBitmap: Bitmap? = null,
imageUri: Uri? = null
imageUri: Uri? = null,
email: String = ""
): IPersona {
val persona = Persona(name)
persona.email = email
persona.subtitle = subtitle
persona.avatarImageResourceId = imageResource
persona.avatarImageDrawable = imageDrawable
persona.avatarImageBitmap = imageBitmap
persona.avatarImageUri = imageUri
return persona
}
internal fun createCustomPersona(
context: Context,
name: String = context.getString(R.string.persona_name_robert_tolbert),
email: String = ""
): IPersona {
val customPersona = CustomPersona(name, email)
customPersona.avatarImageDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_robert_tolbert)
customPersona.description = context.getString(R.string.people_picker_custom_persona_description)
return customPersona
}
internal data class CustomPersona(override var name: String = "", override var email: String = "") : IPersona, Serializable {
var description: String = ""
override var subtitle: String = ""
override var footer: String = ""
override var avatarImageBitmap: Bitmap? = null
override var avatarImageDrawable: Drawable? = null
override var avatarImageResourceId: Int? = null
override var avatarImageUri: Uri? = null
}

Просмотреть файл

@ -27,16 +27,41 @@
<string name="people_picker_select_deselect_example">SelectDeselect</string>
<string name="people_picker_none_example">None</string>
<string name="people_picker_delete_example">Delete</string>
<string name="people_picker_picked_personas_listener">Personas Listener</string>
<string name="people_picker_suggestions_listener">Suggestions Listener</string>
<string name="people_picker_custom_persona_description">This example shows how to create a custom IPersona object.</string>
<string name="people_picker_dialog_title_removed">You removed a persona:</string>
<string name="people_picker_dialog_title_added">You added a persona:</string>
<string name="people_picker_drag_started">Drag started</string>
<string name="people_picker_drag_ended">Drag ended</string>
<string name="people_picker_picked_personas_listener">Personas Listener</string>
<string name="people_picker_suggestions_listener">Suggestions Listener</string>
<!--Persona-->
<string name="persona_email_allan_munger">amunger@microsoft.com</string>
<string name="persona_email_amanda_brady">abrady@microsoft.com</string>
<string name="persona_email_carlos_slattery">cslattery@microsoft.com</string>
<string name="persona_email_carole_poland">cpoland@microsoft.com</string>
<string name="persona_email_cecil_folk">cfolk@microsoft.com</string>
<string name="persona_email_celeste_burton">cburton@microsoft.com</string>
<string name="persona_email_charlotte_waltson">cwaltson@microsoft.com</string>
<string name="persona_email_colin_ballinger">cballinger@microsoft.com</string>
<string name="persona_email_daisy_phillips">dphillips@microsoft.com</string>
<string name="persona_email_elliot_woodward">ewoodward@microsoft.com</string>
<string name="persona_email_elvia_atkins">eatkins@microsoft.com</string>
<string name="persona_email_erik_nason">enason@microsoft.com</string>
<string name="persona_email_henry_brill">hbrill@microsoft.com</string>
<string name="persona_email_isaac_fielder">ifielder@microsoft.com</string>
<string name="persona_email_johnie_mcconnell">jmcconnell@microsoft.com</string>
<string name="persona_email_kat_larsson">klarsson@microsoft.com</string>
<string name="persona_email_katri_ahokas">kahokas@microsoft.com</string>
<string name="persona_email_kevin_sturgis">ksturgis@microsoft.com</string>
<string name="persona_email_kristen_patterson">kpatterson@microsoft.com</string>
<string name="persona_email_lydia_bauer">lbauer@microsoft.com</string>
<string name="persona_email_mauricio_august">maugust@microsoft.com</string>
<string name="persona_email_miguel_garcia">mgarcia@microsoft.com</string>
<string name="persona_email_mona_kane">mkane@microsoft.com</string>
<string name="persona_email_robin_counts">rcounts@microsoft.com</string>
<string name="persona_email_tim_deboer">tdeboer@microsoft.com</string>
<string name="persona_email_wanda_howard">whoward@microsoft.com</string>
<string name="persona_footer">Available</string>
<string name="persona_name_allan_munger">Allan Munger</string>
<string name="persona_name_amanda_brady">Amanda Brady</string>
@ -48,7 +73,7 @@
<string name="persona_name_charlotte_waltson">Charlotte Waltson</string>
<string name="persona_name_colin_ballinger">Colin Ballinger</string>
<string name="persona_name_daisy_phillips">Daisy Phillips</string>
<string name="persona_name_elliot_woordward">Elliot Woodward</string>
<string name="persona_name_elliot_woodward">Elliot Woodward</string>
<string name="persona_name_elvia_atkins">Elvia Atkins</string>
<string name="persona_name_erik_nason">Erik Nason</string>
<string name="persona_name_henry_brill">Henry Brill</string>
@ -63,6 +88,7 @@
<string name="persona_name_miguel_garcia">Miguel Garcia</string>
<string name="persona_name_mona_kane">Mona Kane</string>
<string name="persona_name_robin_counts">Robin Counts</string>
<string name="persona_name_robert_tolbert">Robert Tolbert</string>
<string name="persona_name_tim_deboer">Tim Deboer</string>
<string name="persona_name_wanda_howard">Wanda Howard</string>
<string name="persona_subtitle_designer">Designer</string>

Просмотреть файл

@ -4,22 +4,30 @@
package com.microsoft.officeuifabric.peoplepicker
import android.content.ClipData
import android.content.ClipDescription
import android.content.Context
import android.graphics.Rect
import android.os.Handler
import android.support.v4.content.ContextCompat
import android.text.InputFilter
import android.text.SpannableString
import android.text.Spanned
import android.text.TextUtils
import android.text.method.MovementMethod
import android.text.style.TextAppearanceSpan
import android.text.util.Rfc822Token
import android.text.util.Rfc822Tokenizer
import android.util.AttributeSet
import android.util.Patterns
import android.view.DragEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.accessibility.AccessibilityEvent
import android.view.inputmethod.InputMethodManager
import com.microsoft.officeuifabric.R
import com.microsoft.officeuifabric.persona.IPersona
import com.microsoft.officeuifabric.persona.Persona
import com.microsoft.officeuifabric.persona.PersonaChipView
import com.microsoft.officeuifabric.persona.setPersona
import com.tokenautocomplete.CountSpan
@ -45,9 +53,9 @@ import com.tokenautocomplete.TokenCompleteTextView
*
* TODO Future work:
* - Improve accessibility with something like the [TokenCompleteTextViewTouchHelper] class.
* - Support drag and drop.
* - Limit what appears in the long click context menu.
* - Baseline align chips with other text
* - Baseline align chips with other text.
* - Improve vertical spacing for chips.
*/
internal class PeoplePickerTextView : TokenCompleteTextView<IPersona> {
companion object {
@ -65,6 +73,12 @@ internal class PeoplePickerTextView : TokenCompleteTextView<IPersona> {
field = value
setTokenClickStyle(value)
}
/**
* Flag for enabling Drag and Drop persona chips.
*/
var allowPersonaChipDragAndDrop: Boolean = false
lateinit var onCreatePersona: (name: String, email: String) -> IPersona
private val countSpan: CountSpan?
get() = text.getSpans(0, text.length, CountSpan::class.java).firstOrNull()
@ -101,9 +115,7 @@ internal class PeoplePickerTextView : TokenCompleteTextView<IPersona> {
if (completionText.isEmpty() || !isEmailValid(completionText))
return null
val entry = Persona()
entry.email = completionText
return entry
return onCreatePersona("", completionText)
}
override fun performCollapse(hasFocus: Boolean) {
@ -177,7 +189,7 @@ internal class PeoplePickerTextView : TokenCompleteTextView<IPersona> {
isCursorVisible = false
filters = blockInputFilters
// Prevents other input from being selected when a token is selected
// Prevents other input from being selected when a persona chip is selected
blockedMovementMethod = movementMethod
movementMethod = null
}
@ -190,4 +202,152 @@ internal class PeoplePickerTextView : TokenCompleteTextView<IPersona> {
if (blockedMovementMethod != null)
movementMethod = blockedMovementMethod
}
// Drag and drop
private var isDraggingPersonaChip: Boolean = false
private var firstTouchX: Float = 0f
private var firstTouchY: Float = 0f
private var initialTouchedPersonaSpan: TokenImageSpan? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
var handled = false
if (personaChipClickStyle == TokenClickStyle.None)
handled = super.onTouchEvent(event)
val touchedPersonaSpan = getPersonaSpanAt(event.x, event.y)
if (touchedPersonaSpan != null) {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (!isFocused)
requestFocus()
if (allowPersonaChipDragAndDrop) {
initialTouchedPersonaSpan = touchedPersonaSpan
firstTouchX = event.x
firstTouchY = event.y
parent.requestDisallowInterceptTouchEvent(true)
handled = true
}
}
MotionEvent.ACTION_MOVE -> {
if (allowPersonaChipDragAndDrop && !isDraggingPersonaChip) {
val deltaX = Math.ceil(Math.abs(firstTouchX - event.x).toDouble()).toInt()
val deltaY = Math.ceil(Math.abs(firstTouchY - event.y).toDouble()).toInt()
val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
if (deltaX >= touchSlop || deltaY >= touchSlop)
startPersonaDragAndDrop(touchedPersonaSpan.token)
handled = true
}
}
MotionEvent.ACTION_UP -> {
if (isFocused && text != null && initialTouchedPersonaSpan == touchedPersonaSpan)
touchedPersonaSpan.onClick()
initialTouchedPersonaSpan = null
handled = true
}
MotionEvent.ACTION_CANCEL -> {
initialTouchedPersonaSpan = null
}
}
}
if (!handled && personaChipClickStyle != TokenClickStyle.None)
handled = super.onTouchEvent(event)
return handled
}
override fun onDragEvent(event: DragEvent): Boolean {
if (!allowPersonaChipDragAndDrop)
return false
when (event.action) {
DragEvent.ACTION_DRAG_STARTED -> return event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
DragEvent.ACTION_DRAG_ENTERED -> requestFocus()
DragEvent.ACTION_DROP -> return addPersonaFromDragEvent(event)
DragEvent.ACTION_DRAG_ENDED -> {
if (!event.result && isDraggingPersonaChip)
addPersonaFromDragEvent(event)
isDraggingPersonaChip = false
}
}
return false
}
private fun getClipDataForPersona(persona: IPersona): ClipData? {
val name = persona.name
val email = persona.email
val rfcToken = Rfc822Token(name, email, null)
return ClipData.newPlainText(if (TextUtils.isEmpty(name)) email else name, rfcToken.toString())
}
private fun getPersonaForClipData(clipData: ClipData): IPersona? {
if (!clipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) || clipData.itemCount != 1)
return null
val clipDataItem = clipData.getItemAt(0) ?: return null
val data = clipDataItem.text
if (TextUtils.isEmpty(data))
return null
val rfcTokens = Rfc822Tokenizer.tokenize(data)
if (rfcTokens == null || rfcTokens.isEmpty())
return null
val rfcToken = rfcTokens[0]
return onCreatePersona(rfcToken.name ?: "", rfcToken.address ?: "")
}
private fun startPersonaDragAndDrop(persona: IPersona) {
val clipData = getClipDataForPersona(persona) ?: return
// Layout a copy of the persona chip to use as the drag shadow
val personaChipView = getViewForObject(persona)
personaChipView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
personaChipView.layout(0, 0, personaChipView.measuredWidth, personaChipView.measuredHeight)
personaChipView.background = ContextCompat.getDrawable(context, R.color.uifabric_people_picker_persona_chip_drag_background)
// We pass the persona object as LocalState so we can restore it when dropping
// [startDrag] is deprecated, but the new [startDragAndDrop] requires a higher api than our min
isDraggingPersonaChip = startDrag(clipData, View.DragShadowBuilder(personaChipView), persona, 0)
if (isDraggingPersonaChip)
removeObject(persona)
}
private fun getPersonaSpanAt(x: Float, y: Float): TokenImageSpan? {
if (TextUtils.isEmpty(text))
return null
val offset = getOffsetForPosition(x, y)
if (offset == -1)
return null
val personaSpans = text.getSpans(offset, offset, TokenImageSpan::class.java)
as Array<TokenCompleteTextView<IPersona>.TokenImageSpan>
return personaSpans.firstOrNull()
}
private fun addPersonaFromDragEvent(event: DragEvent): Boolean {
var persona = event.localState as? IPersona
// If it looks like the drag & drop is not coming from us, try to extract a persona object from the clipData
if (persona == null && event.clipData != null)
persona = getPersonaForClipData(event.clipData)
if (persona == null)
return false
addObject(persona)
return true
}
}

Просмотреть файл

@ -14,6 +14,7 @@ import android.widget.Filter
import android.widget.TextView
import com.microsoft.officeuifabric.R
import com.microsoft.officeuifabric.persona.IPersona
import com.microsoft.officeuifabric.persona.Persona
import com.microsoft.officeuifabric.persona.PersonaView
import com.microsoft.officeuifabric.view.TemplateView
import com.tokenautocomplete.TokenCompleteTextView
@ -123,7 +124,28 @@ class PeoplePickerView : TemplateView {
updateViews()
}
/**
* Provides callbacks for when a persona chip is added or removed from the [PeoplePickerTextView].
* Flag for enabling Drag and Drop persona chips.
*/
var allowPersonaChipDragAndDrop: Boolean = false
set(value) {
if (field == value)
return
field = value
updateViews()
}
/**
* Callback to use your own [IPersona] object in place of our default [Persona].
*/
var onCreatePersona: ((name: String, email: String) -> IPersona)? = null
set(value) {
if (field == value)
return
field = value
updateViews()
}
/**
* Callbacks for when a persona chip is added or removed from the [PeoplePickerTextView].
*/
var pickedPersonasChangeListener: PickedPersonasChangeListener? = null
/**
@ -131,7 +153,7 @@ class PeoplePickerView : TemplateView {
*/
var personaSuggestionsListener: PersonaSuggestionsListener? = null
/**
* Use this callback for additional customized filtering when using the [showSearchDirectoryButton].
* Callbacks for additional customized filtering when using the [showSearchDirectoryButton].
*/
var searchDirectorySuggestionsListener: PersonaSuggestionsListener? = null
@ -201,6 +223,8 @@ class PeoplePickerView : TemplateView {
setAdapter(peoplePickerTextViewAdapter)
personaChipClickStyle = this@PeoplePickerView.personaChipClickStyle
hint = valueHint
allowPersonaChipDragAndDrop = this@PeoplePickerView.allowPersonaChipDragAndDrop
onCreatePersona = ::createPersona
}
peoplePickerTextViewAdapter?.showSearchDirectoryButton = showSearchDirectoryButton
}
@ -211,6 +235,10 @@ class PeoplePickerView : TemplateView {
peoplePickerTextView?.addObject(persona)
}
private fun createPersona(name: String, email: String): IPersona {
return onCreatePersona?.invoke(name, email) ?: Persona(name, email)
}
// Dropdown
override fun onConfigurationChanged(newConfig: Configuration) {

Просмотреть файл

@ -118,6 +118,9 @@ open class AvatarView : AppCompatImageView {
}
override fun setImageURI(uri: Uri?) {
if (uri == null)
return
try {
val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
setImageBitmap(bitmap)

Просмотреть файл

@ -36,23 +36,31 @@ class PersonaChipView : TemplateView {
}
var avatarImageBitmap: Bitmap? = null
set(value) {
if (field == value)
return
field = value
avatarView?.avatarImageBitmap = value
updateViews()
}
var avatarImageDrawable: Drawable? = null
set(value) {
if (field == value)
return
field = value
avatarView?.avatarImageDrawable = value
updateViews()
}
var avatarImageResourceId: Int? = null
set(value) {
if (field == value)
return
field = value
avatarView?.avatarImageResourceId = value
updateViews()
}
var avatarImageUri: Uri? = null
set(value) {
if (field == value)
return
field = value
avatarView?.avatarImageUri = value
updateViews()
}
/**
* Flag for setting the chip's error state
@ -93,8 +101,8 @@ class PersonaChipView : TemplateView {
override val templateId: Int = R.layout.view_persona_chip
private var avatarView: AvatarView? = null
private var closeIcon: ImageView? = null
private var textView: TextView? = null
private var closeIcon: ImageView? = null
override fun onTemplateLoaded() {
super.onTemplateLoaded()
@ -216,9 +224,14 @@ class PersonaChipView : TemplateView {
!email.isEmpty() -> email
else -> context.getString(R.string.persona_title_placeholder)
}
avatarView?.name = name
avatarView?.email = email
avatarView?.avatarImageDrawable = avatarImageDrawable
avatarView?.apply {
name = this@PersonaChipView.name
email = this@PersonaChipView.email
avatarImageDrawable = this@PersonaChipView.avatarImageDrawable
avatarImageBitmap = this@PersonaChipView.avatarImageBitmap
avatarImageUri = this@PersonaChipView.avatarImageUri
}
}
interface Listener {

Просмотреть файл

@ -6,6 +6,8 @@
<!--Product Primary-->
<color name="uifabric_primary">#0078D4</color>
<color name="uifabric_primary_dark">#005a9e</color>
<!-- TODO update after design adds Fluent colors to the toolkit -->
<color name="uifabric_primary_highlighted">#CBE3F5</color>
<!--Multi-Use-->
<color name="uifabric_white">#FFFFFF</color>
@ -72,6 +74,7 @@
<!--PeoplePicker-->
<color name="uifabric_people_picker_popup_background">@color/uifabric_white</color>
<color name="uifabric_people_picker_text_view_background">@color/uifabric_white</color>
<color name="uifabric_people_picker_persona_chip_drag_background">@color/uifabric_primary_highlighted</color>
<!--Persona Chip-->
<color name="uifabric_persona_chip_normal">@color/uifabric_background_gray</color>