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:
Родитель
37fb4f5950
Коммит
ea43301431
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче