Add support for drag + drop in the media send flow.

fork-5.53.8
Greyson Parrelli 2021-09-07 19:25:43 -04:00
rodzic 1dbb6013cb
commit ddad9acef1
7 zmienionych plików z 122 dodań i 6 usunięć

Wyświetl plik

@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.livedata.Store
import java.util.Collections
/**
* ViewModel which maintains the list of selected media and other shared values.
@ -62,6 +63,8 @@ class MediaSelectionViewModel(
}
}
private var lastMediaDrag: Pair<Int, Int> = Pair(0, 0)
init {
val recipientId = destination.getRecipientId()
if (recipientId != null) {
@ -123,6 +126,52 @@ class MediaSelectionViewModel(
)
}
fun swapMedia(originalStart: Int, end: Int): Boolean {
var start: Int = originalStart
if (lastMediaDrag.first == start && lastMediaDrag.second == end) {
return true
} else if (lastMediaDrag.first == start) {
start = lastMediaDrag.second
}
val snapshot = store.state
if (end >= snapshot.selectedMedia.size || end < 0 || start >= snapshot.selectedMedia.size || start < 0) {
return false
}
lastMediaDrag = Pair(originalStart, end)
val newMediaList = snapshot.selectedMedia.toMutableList()
if (start < end) {
for (i in start until end) {
Collections.swap(newMediaList, i, i + 1)
}
} else {
for (i in start downTo end + 1) {
Collections.swap(newMediaList, i, i - 1)
}
}
store.update {
it.copy(
selectedMedia = newMediaList
)
}
return true
}
fun isValidMediaDragPosition(position: Int): Boolean {
return position >= 0 && position < store.state.selectedMedia.size
}
fun onMediaDragFinished() {
lastMediaDrag = Pair(0, 0)
}
fun removeMedia(media: Media) {
val snapshot = store.state
val newMediaList = snapshot.selectedMedia - media

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
@ -39,6 +40,8 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
private lateinit var bottomBarGroup: View
private lateinit var selectedRecycler: RecyclerView
private var selectedMediaTouchHelper: ItemTouchHelper? = null
private val galleryAdapter = MappingAdapter()
private val selectedAdapter = MappingAdapter()
@ -88,6 +91,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
callbacks.onSelectedMediaClicked(media)
}
selectedRecycler.adapter = selectedAdapter
selectedMediaTouchHelper?.attachToRecyclerView(selectedRecycler)
MediaGallerySelectableItem.registerAdapter(
mappingAdapter = galleryAdapter,
@ -153,6 +157,10 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
viewStateLiveData.value = state
}
fun bindSelectedMediaItemDragHelper(helper: ItemTouchHelper) {
selectedMediaTouchHelper = helper
}
data class ViewState(
val selectedMedia: List<Media> = listOf()
)

Wyświetl plik

@ -5,11 +5,13 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator.Companion.requestPermissionsForCamera
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
import org.thoughtcrime.securesms.mediasend.v2.review.MediaSelectionItemTouchHelper
import org.thoughtcrime.securesms.permissions.Permissions
private const val MEDIA_GALLERY_TAG = "MEDIA_GALLERY"
@ -29,6 +31,8 @@ class MediaSelectionGalleryFragment : Fragment(R.layout.fragment_container), Med
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mediaGalleryFragment = ensureMediaGalleryFragment()
mediaGalleryFragment.bindSelectedMediaItemDragHelper(ItemTouchHelper(MediaSelectionItemTouchHelper(sharedViewModel)))
sharedViewModel.state.observe(viewLifecycleOwner) { state ->
mediaGalleryFragment.onViewStateUpdated(MediaGalleryFragment.ViewState(state.selectedMedia))
}

Wyświetl plik

@ -15,6 +15,7 @@ import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -181,6 +182,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
}
}
selectionRecycler.adapter = selectionAdapter
ItemTouchHelper(MediaSelectionItemTouchHelper(sharedViewModel)).attachToRecyclerView(selectionRecycler)
sharedViewModel.state.observe(viewLifecycleOwner) { state ->
pagerAdapter.submitMedia(state.selectedMedia)

Wyświetl plik

@ -0,0 +1,54 @@
package org.thoughtcrime.securesms.mediasend.v2.review;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel;
/**
* A touch helper for handling drag + drop on the media rail in the media send flow.
*/
public class MediaSelectionItemTouchHelper extends ItemTouchHelper.Callback {
private final MediaSelectionViewModel viewModel;
public MediaSelectionItemTouchHelper(MediaSelectionViewModel viewModel) {
this.viewModel = viewModel;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (viewModel.isValidMediaDragPosition(viewHolder.getAdapterPosition())) {
int dragFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
return makeMovementFlags(dragFlags, 0);
} else {
return 0;
}
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return viewModel.swapMedia(viewHolder.getAdapterPosition(), target.getAdapterPosition());
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewModel.onMediaDragFinished();
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
}

Wyświetl plik

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.recipients;
import static org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@ -32,7 +34,6 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.MentionSetting;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor;
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
@ -59,12 +60,9 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBannerTier;
public class Recipient {
private static final String TAG = Log.tag(Recipient.class);

Wyświetl plik

@ -55,12 +55,13 @@
android:id="@+id/selection_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:layout_marginBottom="2dp"
android:alpha="0"
android:orientation="horizontal"
android:visibility="gone"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@id/controls_shade"
tools:alpha="1"