Add double tap editing feature.
This commit is contained in:
parent
84e654efb2
commit
ffc1463cda
13 changed files with 220 additions and 15 deletions
|
@ -328,5 +328,7 @@ class V2ConversationItemShapeTest {
|
|||
override fun onReportSpamLearnMoreClicked() = Unit
|
||||
|
||||
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||
|
||||
override fun onItemDoubleClick(item: MultiselectPart) = Unit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,6 +300,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
|||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onItemDoubleClick(item: MultiselectPart) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onShowSafetyTips(forGroup: Boolean) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
@ -126,5 +126,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
|||
void onShowSafetyTips(boolean forGroup);
|
||||
void onReportSpamLearnMoreClicked();
|
||||
void onMessageRequestAcceptOptionsClicked();
|
||||
void onItemDoubleClick(MultiselectPart multiselectPart);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import android.text.style.StyleSpan;
|
|||
import android.text.style.URLSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.TouchDelegate;
|
||||
|
@ -256,6 +257,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
private final UrlClickListener urlClickListener = new UrlClickListener();
|
||||
private final Rect thumbnailMaskingRect = new Rect();
|
||||
private final TouchDelegateChangedListener touchDelegateChangedListener = new TouchDelegateChangedListener();
|
||||
private final DoubleTapEditTouchListener doubleTapEditTouchListener = new DoubleTapEditTouchListener();
|
||||
private final GiftMessageViewCallback giftMessageViewCallback = new GiftMessageViewCallback();
|
||||
|
||||
private final Context context;
|
||||
|
@ -351,6 +353,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
|
||||
setOnClickListener(new ClickListener(null));
|
||||
|
||||
bodyText.setOnTouchListener(doubleTapEditTouchListener);
|
||||
bodyText.setOnLongClickListener(passthroughClickListener);
|
||||
bodyText.setOnClickListener(passthroughClickListener);
|
||||
footer.setOnTouchDelegateChangedListener(touchDelegateChangedListener);
|
||||
|
@ -2438,6 +2441,24 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
}
|
||||
}
|
||||
|
||||
private class DoubleTapEditTouchListener implements View.OnTouchListener {
|
||||
private final GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
if (eventListener != null && batchSelected.isEmpty()) {
|
||||
eventListener.onItemDoubleClick(getMultiselectPartForLatestTouch());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private class AttachmentDownloadClickListener implements SlidesClickedListener {
|
||||
@Override
|
||||
public void onClick(View v, final List<Slide> slides) {
|
||||
|
|
|
@ -277,6 +277,7 @@ class ScheduledMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment
|
|||
override fun onShowSafetyTips(forGroup: Boolean) = Unit
|
||||
override fun onReportSpamLearnMoreClicked() = Unit
|
||||
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||
override fun onItemDoubleClick(item: MultiselectPart) = Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -261,6 +261,7 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {
|
|||
override fun onShowSafetyTips(forGroup: Boolean) = Unit
|
||||
override fun onReportSpamLearnMoreClicked() = Unit
|
||||
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||
override fun onItemDoubleClick(item: MultiselectPart) = Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -168,6 +168,7 @@ class EditMessageHistoryDialog : FixedRoundedCornerBottomSheetDialogFragment() {
|
|||
override fun onShowSafetyTips(forGroup: Boolean) = Unit
|
||||
override fun onReportSpamLearnMoreClicked() = Unit
|
||||
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||
override fun onItemDoubleClick(item: MultiselectPart) = Unit
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -351,7 +351,8 @@ class ConversationFragment :
|
|||
ConversationBottomSheetCallback,
|
||||
SafetyNumberBottomSheet.Callbacks,
|
||||
EnableCallNotificationSettingsDialog.Callback,
|
||||
MultiselectForwardBottomSheet.Callback {
|
||||
MultiselectForwardBottomSheet.Callback,
|
||||
DoubleTapEditEducationSheet.Callback {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||
|
@ -2755,6 +2756,20 @@ class ConversationFragment :
|
|||
RecipientBottomSheetDialogFragment.show(childFragmentManager, recipientId, groupId)
|
||||
}
|
||||
|
||||
override fun onItemDoubleClick(item: MultiselectPart) {
|
||||
Log.d(TAG, "onItemDoubleClick")
|
||||
if (!isValidEditMessageSend(item.getMessageRecord(), System.currentTimeMillis())) {
|
||||
return
|
||||
}
|
||||
|
||||
if (SignalStore.uiHints().hasSeenDoubleTapEditEducationSheet) {
|
||||
onDoubleTapEditEducationSheetNext(item.conversationMessage)
|
||||
return
|
||||
}
|
||||
|
||||
DoubleTapEditEducationSheet(item).show(childFragmentManager, DoubleTapEditEducationSheet.KEY)
|
||||
}
|
||||
|
||||
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
|
||||
val recipientId = viewModel.recipientSnapshot?.id ?: return
|
||||
if (messageRecord.isIdentityMismatchFailure) {
|
||||
|
@ -4307,4 +4322,8 @@ class ConversationFragment :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDoubleTapEditEducationSheetNext(conversationMessage: ConversationMessage) {
|
||||
handleEditMessage(conversationMessage)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
|
||||
/**
|
||||
* Shows an education sheet to users explaining how double tapping a sent message within 24hrs will allow them to edit it
|
||||
*/
|
||||
class DoubleTapEditEducationSheet(private val item: MultiselectPart) : FixedRoundedCornerBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
const val KEY = "DOUBLE_TAP_EDIT_EDU"
|
||||
}
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.conversation_item_double_tap_edit_education_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
SignalStore.uiHints().hasSeenDoubleTapEditEducationSheet = true
|
||||
|
||||
view.findViewById<MaterialButton>(R.id.got_it).setOnClickListener {
|
||||
requireListener<Callback>().onDoubleTapEditEducationSheetNext(item.conversationMessage)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
super.onCancel(dialog)
|
||||
requireListener<Callback>().onDoubleTapEditEducationSheetNext(item.conversationMessage)
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onDoubleTapEditEducationSheetNext(conversationMessage: ConversationMessage)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@ import android.text.style.ClickableSpan
|
|||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.util.TypedValue
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -110,6 +112,19 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
private val senderDrawable = ChatColorsDrawable(conversationContext::getChatColorsData)
|
||||
private val bodyBubbleLayoutTransition = BodyBubbleLayoutTransition()
|
||||
|
||||
private val gestureDetector = GestureDetector(
|
||||
context,
|
||||
object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||
if (conversationContext.selectedItems.isEmpty()) {
|
||||
conversationContext.clickListener.onItemDoubleClick(getMultiselectPartForLatestTouch())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
protected lateinit var shape: V2ConversationItemShape.MessageShape
|
||||
|
||||
private val replyDelegate = object : V2ConversationItemLayout.OnMeasureListener {
|
||||
|
@ -139,6 +154,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
)
|
||||
}
|
||||
|
||||
binding.body.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
||||
binding.root.setOnClickListener { onBubbleClicked() }
|
||||
binding.root.setOnLongClickListener {
|
||||
conversationContext.clickListener.onItemLongClick(binding.root, getMultiselectPartForLatestTouch())
|
||||
|
|
|
@ -9,20 +9,21 @@ public class UiHints extends SignalStoreValues {
|
|||
|
||||
private static final int NEVER_DISPLAY_PULL_TO_FILTER_TIP_THRESHOLD = 3;
|
||||
|
||||
private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
|
||||
private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
|
||||
private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
|
||||
private static final String NEVER_DISPLAY_PULL_TO_FILTER_TIP = "uihints.never_display_pull_to_filter_tip";
|
||||
private static final String HAS_SEEN_SCHEDULED_MESSAGES_INFO_ONCE = "uihints.has_seen_scheduled_messages_info_once";
|
||||
private static final String HAS_SEEN_TEXT_FORMATTING_ALERT = "uihints.text_formatting.has_seen_alert";
|
||||
private static final String HAS_NOT_SEEN_EDIT_MESSAGE_BETA_ALERT = "uihints.edit_message.has_not_seen_beta_alert";
|
||||
private static final String HAS_SEEN_SAFETY_NUMBER_NUX = "uihints.has_seen_safety_number_nux";
|
||||
private static final String DECLINED_NOTIFICATION_LOGS_PROMPT = "uihints.declined_notification_logs";
|
||||
private static final String LAST_NOTIFICATION_LOGS_PROMPT_TIME = "uihints.last_notification_logs_prompt";
|
||||
private static final String DISMISSED_BATTERY_SAVER_PROMPT = "uihints.declined_battery_saver_prompt";
|
||||
private static final String LAST_BATTERY_SAVER_PROMPT = "uihints.last_battery_saver_prompt";
|
||||
private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
|
||||
private static final String HAS_COMPLETED_USERNAME_ONBOARDING = "uihints.has_completed_username_onboarding";
|
||||
private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
|
||||
private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
|
||||
private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
|
||||
private static final String NEVER_DISPLAY_PULL_TO_FILTER_TIP = "uihints.never_display_pull_to_filter_tip";
|
||||
private static final String HAS_SEEN_SCHEDULED_MESSAGES_INFO_ONCE = "uihints.has_seen_scheduled_messages_info_once";
|
||||
private static final String HAS_SEEN_TEXT_FORMATTING_ALERT = "uihints.text_formatting.has_seen_alert";
|
||||
private static final String HAS_NOT_SEEN_EDIT_MESSAGE_BETA_ALERT = "uihints.edit_message.has_not_seen_beta_alert";
|
||||
private static final String HAS_SEEN_SAFETY_NUMBER_NUX = "uihints.has_seen_safety_number_nux";
|
||||
private static final String DECLINED_NOTIFICATION_LOGS_PROMPT = "uihints.declined_notification_logs";
|
||||
private static final String LAST_NOTIFICATION_LOGS_PROMPT_TIME = "uihints.last_notification_logs_prompt";
|
||||
private static final String DISMISSED_BATTERY_SAVER_PROMPT = "uihints.declined_battery_saver_prompt";
|
||||
private static final String LAST_BATTERY_SAVER_PROMPT = "uihints.last_battery_saver_prompt";
|
||||
private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
|
||||
private static final String HAS_COMPLETED_USERNAME_ONBOARDING = "uihints.has_completed_username_onboarding";
|
||||
private static final String HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET = "uihints.has_seen_double_tap_edit_education_sheet";
|
||||
|
||||
UiHints(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -158,4 +159,12 @@ public class UiHints extends SignalStoreValues {
|
|||
public long getLastCrashPrompt() {
|
||||
return getLong(LAST_CRASH_PROMPT, 0);
|
||||
}
|
||||
|
||||
public void setHasSeenDoubleTapEditEducationSheet(boolean seen) {
|
||||
putBoolean(HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET, seen);
|
||||
}
|
||||
|
||||
public boolean getHasSeenDoubleTapEditEducationSheet() {
|
||||
return getBoolean(HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/bottom_sheet_handle" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/handle"
|
||||
app:tint="@color/signal_icon_tint_primary"
|
||||
app:srcCompat="@drawable/ic_tap_outline_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/DoubleTapEditEducationSheet__double_tap_edit_title"
|
||||
android:textAppearance="@style/Signal.Text.TitleLarge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/image" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/double_tap_details"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/DoubleTapEditEducationSheet__quickly_tap_twice"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/signal_colorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/got_it"
|
||||
style="@style/Signal.Widget.Button.Medium.Primary"
|
||||
android:backgroundTint="@color/signal_colorPrimaryContainer"
|
||||
android:textColor="@color/signal_colorOnPrimaryContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DoubleTapEditEducationSheet__got_it"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/double_tap_details" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -3691,6 +3691,14 @@
|
|||
<string name="conversation_group_options__conversation">Chat</string>
|
||||
<string name="conversation_group_options__broadcast">Broadcast</string>
|
||||
|
||||
<!-- DoubleTapEditEducationSheet -->
|
||||
<!-- Displayed as the title of the education bottom sheet -->
|
||||
<string name="DoubleTapEditEducationSheet__double_tap_edit_title">Double tap to edit</string>
|
||||
<!-- Text on the sheet explaining how double tapping on a message will let them edit it -->
|
||||
<string name="DoubleTapEditEducationSheet__quickly_tap_twice">Quickly tap twice on your messages to edit them. You can edit your messages up to 24hrs after they’ve been sent.</string>
|
||||
<!-- Button label to dismiss sheet -->
|
||||
<string name="DoubleTapEditEducationSheet__got_it">Got it</string>
|
||||
|
||||
<!-- text_secure_normal -->
|
||||
<string name="text_secure_normal__menu_new_group">New group</string>
|
||||
<string name="text_secure_normal__menu_settings">Settings</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue