添付メディア並び替えで、並び替えせずにスクロールしたい場合がある
This commit is contained in:
parent
acfd4dd9a0
commit
870dc6186d
|
@ -5,17 +5,18 @@ import android.app.Dialog
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import jp.juggler.subwaytooter.ActPost
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.databinding.AttachmentRearrangeDialogBinding
|
import jp.juggler.subwaytooter.databinding.AttachmentRearrangeDialogBinding
|
||||||
import jp.juggler.subwaytooter.databinding.AttachmentsRearrangeItemBinding
|
import jp.juggler.subwaytooter.databinding.AttachmentsRearrangeItemBinding
|
||||||
import jp.juggler.subwaytooter.defaultColorIcon
|
import jp.juggler.subwaytooter.defaultColorIcon
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.util.data.ellipsizeDot3
|
import jp.juggler.util.data.ellipsizeDot3
|
||||||
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.attrColor
|
import jp.juggler.util.ui.attrColor
|
||||||
import jp.juggler.util.ui.dismissSafe
|
import jp.juggler.util.ui.dismissSafe
|
||||||
import jp.juggler.util.ui.dp
|
import jp.juggler.util.ui.dp
|
||||||
|
@ -24,56 +25,60 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import org.jetbrains.anko.backgroundColor
|
import org.jetbrains.anko.backgroundColor
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
suspend fun ActPost.dialogArrachmentRearrange(
|
private val log = LogCategory("DlgAttachmentRearrange")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 投稿画面で添付メディアを並べ替えるダイアログを開き、OKボタンが押されるまで非同期待機する。
|
||||||
|
* OK以外の方法で閉じたらCancellationExceptionを投げる。
|
||||||
|
*/
|
||||||
|
suspend fun AppCompatActivity.dialogArrachmentRearrange(
|
||||||
initialList: List<PostAttachment>,
|
initialList: List<PostAttachment>,
|
||||||
): List<PostAttachment> = suspendCancellableCoroutine { cont ->
|
): List<PostAttachment> = suspendCancellableCoroutine { cont ->
|
||||||
val views = AttachmentRearrangeDialogBinding.inflate(layoutInflater)
|
val views = AttachmentRearrangeDialogBinding.inflate(layoutInflater)
|
||||||
val dialog = Dialog(this)
|
val dialog = Dialog(this).apply {
|
||||||
dialog.setContentView(views.root)
|
setContentView(views.root)
|
||||||
|
setOnDismissListener {
|
||||||
|
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||||
|
|
||||||
dialog.setOnDismissListener {
|
val myAdapter = RearrangeAdapter(layoutInflater, initialList)
|
||||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
|
||||||
}
|
|
||||||
|
|
||||||
val rearrangeAdapter = RearrangeAdapter(layoutInflater, initialList)
|
|
||||||
|
|
||||||
views.btnCancel.setOnClickListener {
|
views.btnCancel.setOnClickListener {
|
||||||
dialog.dismissSafe()
|
dialog.dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
views.btnOk.setOnClickListener {
|
views.btnOk.setOnClickListener {
|
||||||
if (cont.isActive) {
|
if (cont.isActive) cont.resume(myAdapter.list) {}
|
||||||
cont.resume(rearrangeAdapter.list) {}
|
|
||||||
}
|
|
||||||
dialog.dismissSafe()
|
dialog.dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
views.listView.apply {
|
views.listView.apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = rearrangeAdapter
|
adapter = myAdapter
|
||||||
rearrangeAdapter.itemTouchHelper.attachToRecyclerView(this)
|
myAdapter.itemTouchHelper.attachToRecyclerView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.window?.setLayout(dp(300), dp(440))
|
dialog.window?.setLayout(dp(300), dp(440))
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 並べ替えダイアログ内部のRecyclerViewに使うAdapter
|
||||||
|
*/
|
||||||
private class RearrangeAdapter(
|
private class RearrangeAdapter(
|
||||||
private val inflater: LayoutInflater,
|
private val inflater: LayoutInflater,
|
||||||
initialList: List<PostAttachment>,
|
initialList: List<PostAttachment>,
|
||||||
) : RecyclerView.Adapter<RearrangeAdapter.MyViewHolder>(),
|
) : RecyclerView.Adapter<RearrangeAdapter.MyViewHolder>(), MyDragCallback.Changer {
|
||||||
MyDragCallback.Changer {
|
|
||||||
|
|
||||||
val list = ArrayList(initialList)
|
val list = ArrayList(initialList)
|
||||||
|
|
||||||
private var lastStateViewHolder: MyViewHolder? = null
|
private var lastStateViewHolder: MyViewHolder? = null
|
||||||
private var draggingItem: PostAttachment? = null
|
private var draggingItem: PostAttachment? = null
|
||||||
|
|
||||||
val itemTouchHelper by lazy {
|
val itemTouchHelper = ItemTouchHelper(MyDragCallback(this))
|
||||||
ItemTouchHelper(MyDragCallback(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = list.size
|
override fun getItemCount() = list.size
|
||||||
|
|
||||||
|
@ -84,6 +89,7 @@ private class RearrangeAdapter(
|
||||||
holder.bind(list.elementAtOrNull(position))
|
holder.bind(list.elementAtOrNull(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implements MyDragCallback.Changer
|
||||||
override fun onMove(posFrom: Int, posTo: Int): Boolean {
|
override fun onMove(posFrom: Int, posTo: Int): Boolean {
|
||||||
val item = list.removeAt(posFrom)
|
val item = list.removeAt(posFrom)
|
||||||
list.add(posTo, item)
|
list.add(posTo, item)
|
||||||
|
@ -91,18 +97,32 @@ private class RearrangeAdapter(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onState(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
// implements MyDragCallback.Changer
|
||||||
|
override fun onState(
|
||||||
|
caller: String,
|
||||||
|
viewHolder: RecyclerView.ViewHolder?,
|
||||||
|
actionState: Int,
|
||||||
|
) {
|
||||||
|
log.d("onState: caller=$caller, viewHolder=$viewHolder, actionState=$actionState")
|
||||||
|
|
||||||
val holder = (viewHolder as? MyViewHolder)
|
val holder = (viewHolder as? MyViewHolder)
|
||||||
|
// 最後にドラッグ対象となったViewHolderを覚えておく
|
||||||
holder?.let { lastStateViewHolder = it }
|
holder?.let { lastStateViewHolder = it }
|
||||||
|
// 現在ドラッグ対象のPostAttachmentを覚えておく
|
||||||
val pa = holder?.lastItem
|
val pa = holder?.lastItem
|
||||||
draggingItem = when {
|
draggingItem = when {
|
||||||
pa != null && actionState == ItemTouchHelper.ACTION_STATE_DRAG -> pa
|
pa != null && actionState == ItemTouchHelper.ACTION_STATE_DRAG -> pa
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
// 表示の更新
|
||||||
holder?.bind()
|
holder?.bind()
|
||||||
lastStateViewHolder?.takeIf { it != holder }?.bind()
|
lastStateViewHolder?.takeIf { it != holder }?.bind()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val iconPlaceHolder = defaultColorIcon(inflater.context, R.drawable.ic_hourglass)
|
||||||
|
private val iconError = defaultColorIcon(inflater.context, R.drawable.ic_error)
|
||||||
|
private val iconFallback = defaultColorIcon(inflater.context, R.drawable.ic_clip)
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
inner class MyViewHolder(
|
inner class MyViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
|
@ -113,6 +133,7 @@ private class RearrangeAdapter(
|
||||||
var lastItem: PostAttachment? = null
|
var lastItem: PostAttachment? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// リスト項目のタッチですぐにドラッグを開始する
|
||||||
views.root.setOnTouchListener { _, event ->
|
views.root.setOnTouchListener { _, event ->
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
itemTouchHelper.startDrag(this)
|
itemTouchHelper.startDrag(this)
|
||||||
|
@ -121,59 +142,71 @@ private class RearrangeAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(pa: PostAttachment? = lastItem) {
|
fun bind(item: PostAttachment? = lastItem) {
|
||||||
pa ?: return
|
item ?: return
|
||||||
lastItem = pa
|
lastItem = item
|
||||||
|
|
||||||
val context = views.root.context
|
val context = views.root.context
|
||||||
|
|
||||||
|
// ドラッグ中は背景色を変える
|
||||||
views.root.apply {
|
views.root.apply {
|
||||||
when {
|
when {
|
||||||
draggingItem === pa -> backgroundColor =
|
draggingItem === item -> backgroundColor =
|
||||||
context.attrColor(R.attr.colorSearchFormBackground)
|
context.attrColor(R.attr.colorSearchFormBackground)
|
||||||
|
|
||||||
else -> background = null
|
else -> background = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// サムネイルのロード開始
|
||||||
views.ivThumbnail.apply {
|
views.ivThumbnail.apply {
|
||||||
val imageUrl = pa.attachment?.preview_url
|
when (val imageUrl = item.attachment?.preview_url) {
|
||||||
if (imageUrl.isNullOrEmpty()) {
|
null, "" -> {
|
||||||
val imageId = when (pa.status) {
|
val iconDrawable = when (item.status) {
|
||||||
PostAttachment.Status.Progress -> R.drawable.ic_upload
|
PostAttachment.Status.Progress -> iconPlaceHolder
|
||||||
PostAttachment.Status.Error -> R.drawable.ic_error
|
PostAttachment.Status.Error -> iconError
|
||||||
else -> R.drawable.ic_clip
|
else -> iconFallback
|
||||||
|
}
|
||||||
|
Glide.with(context).clear(this)
|
||||||
|
setImageDrawable(iconDrawable)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(imageUrl)
|
||||||
|
.placeholder(iconPlaceHolder)
|
||||||
|
.error(iconError)
|
||||||
|
.fallback(iconFallback)
|
||||||
|
.into(this)
|
||||||
}
|
}
|
||||||
Glide.with(context).clear(this)
|
|
||||||
setImageDrawable(defaultColorIcon(context, imageId))
|
|
||||||
} else {
|
|
||||||
Glide.with(context)
|
|
||||||
.load(imageUrl)
|
|
||||||
.placeholder(defaultColorIcon(context, R.drawable.ic_hourglass))
|
|
||||||
.error(defaultColorIcon(context, R.drawable.ic_error))
|
|
||||||
.fallback(defaultColorIcon(context, R.drawable.ic_clip))
|
|
||||||
.into(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
views.tvText.text = pa.attachment?.run {
|
|
||||||
"$type ${description?.ellipsizeDot3(40) ?: ""}"
|
// テキストの表示
|
||||||
} ?: context.getString(R.string.attachment_uploading)
|
views.tvText.text = item.attachment?.run {
|
||||||
|
"${type.id} ${description?.ellipsizeDot3(40) ?: ""}"
|
||||||
|
} ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RectclerViewのDrag&Drop操作に関するコールバック
|
||||||
|
*/
|
||||||
private class MyDragCallback(
|
private class MyDragCallback(
|
||||||
private val changer: Changer,
|
private val changer: Changer,
|
||||||
) : ItemTouchHelper.SimpleCallback(
|
) : ItemTouchHelper.SimpleCallback(
|
||||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||||
0 // no swipe
|
0 // no swipe
|
||||||
) {
|
) {
|
||||||
|
// アダプタに行わせたい処理のinterface
|
||||||
interface Changer {
|
interface Changer {
|
||||||
fun onMove(posFrom: Int, posTo: Int): Boolean
|
fun onMove(posFrom: Int, posTo: Int): Boolean
|
||||||
fun onState(viewHolder: RecyclerView.ViewHolder?, actionState: Int)
|
fun onState(caller: String, viewHolder: RecyclerView.ViewHolder?, actionState: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isLongPressDragEnabled() = false
|
override fun isLongPressDragEnabled() = false
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||||
|
|
||||||
override fun onMove(
|
override fun onMove(
|
||||||
|
@ -192,7 +225,7 @@ private class MyDragCallback(
|
||||||
actionState: Int,
|
actionState: Int,
|
||||||
) {
|
) {
|
||||||
super.onSelectedChanged(viewHolder, actionState)
|
super.onSelectedChanged(viewHolder, actionState)
|
||||||
changer.onState(viewHolder, actionState)
|
changer.onState("onSelectedChanged", viewHolder, actionState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView(
|
override fun clearView(
|
||||||
|
@ -200,6 +233,6 @@ private class MyDragCallback(
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
) {
|
) {
|
||||||
super.clearView(recyclerView, viewHolder)
|
super.clearView(recyclerView, viewHolder)
|
||||||
changer.onState(null, ItemTouchHelper.ACTION_STATE_IDLE)
|
changer.onState("clearView", viewHolder, ItemTouchHelper.ACTION_STATE_IDLE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/btnAttachmentsRearrange"
|
android:id="@+id/btnAttachmentsRearrange"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="48dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||||
android:contentDescription="@string/rearrange"
|
android:contentDescription="@string/rearrange"
|
||||||
|
|
|
@ -9,47 +9,60 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="?attr/colorTextContent"
|
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
android:text="@string/attachment_rearrange_desc"
|
android:text="@string/attachment_rearrange_desc"
|
||||||
/>
|
android:textColor="?attr/colorTextContent" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
並べ替えのRecyclerView
|
||||||
|
ドラッグせずにスクロールするためのタッチ領域を確保したいので、右端のpaddingが大きい
|
||||||
|
-->
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/listView"
|
android:id="@+id/listView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:padding="8dp"
|
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
/>
|
android:fadeScrollbars="false"
|
||||||
|
android:paddingEnd="48dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:scrollbarStyle="outsideOverlay"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="?attr/colorSettingDivider"/>
|
android:background="?attr/colorSettingDivider" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal">
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
android:id="@+id/btnCancel"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:id="@+id/btnCancel"
|
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||||
android:text="@string/cancel"/>
|
android:textColor="?attr/colorTextContent"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="1dp"
|
android:layout_width="1dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSettingDivider"/>
|
android:background="?attr/colorSettingDivider" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
android:id="@+id/btnOk"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:id="@+id/btnOk"
|
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||||
android:text="@string/ok"/>
|
android:textColor="?attr/colorTextContent"
|
||||||
|
android:text="@string/ok" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
Loading…
Reference in New Issue