diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java index f130fc7425..0a34339823 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java @@ -175,6 +175,7 @@ public void setParentOnMeasureDisabled (boolean disabled) { @Override public void performDestroy () { + cancelPendingMessageClick(); avatarReceiver.destroy(); avatarsReceiver.performDestroy(); giveawayAvatarsReceiver.performDestroy(); @@ -288,6 +289,9 @@ public final int getCurrentHeight () { } public void setMessage (TGMessage message) { + if (this.msg != message) { + cancelPendingMessageClick(); + } int desiredHeight = message.getHeight(); int currentHeight = getCurrentHeight(); @@ -509,6 +513,7 @@ public void onAttachedToRecyclerView () { } public void onDetachedFromRecyclerView () { + cancelPendingMessageClick(); if (isAttached) { isAttached = false; avatarReceiver.detach(); @@ -536,6 +541,67 @@ public boolean isAttached () { } private float touchX, touchY; + private Runnable pendingMessageClick; + private long pendingMessageClickTime; + private long pendingMessageClickId; + private float pendingMessageClickX, pendingMessageClickY; + + private void cancelPendingMessageClick () { + if (pendingMessageClick != null) { + removeCallbacks(pendingMessageClick); + pendingMessageClick = null; + } + pendingMessageClickTime = 0; + pendingMessageClickId = 0; + } + + private boolean handleMessageTap (float x, float y, long eventTime) { + if (msg == null || !Settings.instance().isQuickReactionDoubleTapEnabled() || Settings.instance().getQuickReactions(msg.tdlib()).length == 0) { + if (onMessageClick(x, y)) { + ViewUtils.onClick(this); + return true; + } + return false; + } + + int doubleTapSlop = ViewConfiguration.get(getContext()).getScaledDoubleTapSlop(); + float dx = x - pendingMessageClickX; + float dy = y - pendingMessageClickY; + boolean isDoubleTap = pendingMessageClick != null && pendingMessageClickId == msg.getId() && + eventTime - pendingMessageClickTime <= ViewConfiguration.getDoubleTapTimeout() && + dx * dx + dy * dy <= doubleTapSlop * doubleTapSlop; + if (isDoubleTap) { + cancelPendingMessageClick(); + if (msg.performDoubleTapQuickReaction()) { + ViewUtils.onClick(this); + return true; + } + return false; + } + + if (pendingMessageClick != null) { + Runnable previousClick = pendingMessageClick; + cancelPendingMessageClick(); + previousClick.run(); + return true; + } + + TGMessage tappedMessage = msg; + pendingMessageClickTime = eventTime; + pendingMessageClickId = msg.getId(); + pendingMessageClickX = x; + pendingMessageClickY = y; + pendingMessageClick = () -> { + pendingMessageClick = null; + pendingMessageClickTime = 0; + pendingMessageClickId = 0; + if (msg == tappedMessage && onMessageClick(x, y)) { + ViewUtils.onClick(this); + } + }; + postDelayed(pendingMessageClick, ViewConfiguration.getDoubleTapTimeout()); + return true; + } private static void selectMessage (MessagesController m, TGMessage msg, float touchX, float touchY) { long messageId = msg.findMessageIdUnder(touchX, touchY); @@ -1622,10 +1688,7 @@ public boolean onTouchEvent (MotionEvent e) { } if ((flags & FLAG_CAUGHT_CLICK) != 0) { flags &= ~FLAG_CAUGHT_CLICK; - if (onMessageClick(e.getX(), e.getY())) { - ViewUtils.onClick(this); - return true; - } + return handleMessageTap(e.getX(), e.getY(), e.getEventTime()); } return false; } diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java index 2cb4dd9e25..ee69c9fcc8 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java @@ -3381,10 +3381,12 @@ public static int getAnchorHighlightMode (int accountId, TdApi.Chat chat, @Nulla if (chat == null) { return HIGHLIGHT_MODE_NONE; } - if (topicId != null && topicId.getConstructor() == TdApi.MessageTopicForum.CONSTRUCTOR) { - return canGoUnread(chat, null, forumTopic) ? HIGHLIGHT_MODE_UNREAD : HIGHLIGHT_MODE_NONE; - } - boolean canGoUnread = canGoUnread(chat, threadInfo, forumTopic); + boolean isForumTopic = topicId != null && topicId.getConstructor() == TdApi.MessageTopicForum.CONSTRUCTOR; + // A pinned forum topic is opened before its ForumTopic object is loaded. In that case, + // falling back to chat-wide unread state both ignores the topic and skips its saved anchor. + boolean canGoUnread = isForumTopic ? + forumTopic != null && canGoUnread(chat, null, forumTopic) : + canGoUnread(chat, threadInfo, forumTopic); Settings.SavedMessageId messageId = Settings.instance().getScrollMessageId(accountId, chat.id, topicId ); diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java index 6b8f201475..51e943c1a7 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java @@ -8896,6 +8896,46 @@ private boolean canSendReaction (TdApi.ReactionType reactionType) { return canBeReacted() && !tdlib.isSelfChat(msg.chatId) && Td.isAvailable(messageAvailableReactions, reactionType); } + private void performQuickReaction (View sourceView, TdApi.ReactionType reactionType, TGReaction reactionObj) { + boolean hasReaction = messageReactions.hasReaction(reactionType); + if (Config.DISABLE_ANONYMOUS_NON_OWNER_REACTIONS && !hasReaction && tdlib.isAnonymousAdminNonCreator(msg.chatId)) { + showContentHint(sourceView, null, R.string.error_ANONYMOUS_REACTIONS_DISABLED); + } else if (!Config.PROTECT_ANONYMOUS_REACTIONS || hasReaction || !canGetAddedReactions() || messagesController().callNonAnonymousProtection(getId() + reactionObj.hashCode(), null)) { + if (messageReactions.toggleReaction(reactionType, false, false, handler(sourceView, null, () -> {}))) { + scheduleSetReactionAnimation(new NextReactionAnimation(reactionObj, NextReactionAnimation.TYPE_QUICK)); + } + } + } + + public boolean performDoubleTapQuickReaction () { + if (!Settings.instance().isQuickReactionDoubleTapEnabled()) { + return false; + } + String[] quickReactions = Settings.instance().getQuickReactions(tdlib); + if (quickReactions.length == 0) { + return false; + } + TdApi.ReactionType reactionType = TD.toReactionType(quickReactions[0]); + TGReaction reactionObj = tdlib.getReaction(reactionType); + if (reactionObj == null) { + return false; + } + Runnable applyReaction = () -> { + if (!isDestroyed() && canSendReaction(reactionType)) { + View currentView = findCurrentView(); + if (currentView != null) { + performQuickReaction(currentView, reactionType, reactionObj); + } + } + }; + if (messageAvailableReactions == null) { + loadAvailableReactions(applyReaction); + } else { + applyReaction.run(); + } + return true; + } + private void computeQuickButtons () { if (!UI.inUiThread()) { tdlib.ui().post(this::computeQuickButtons); @@ -8935,7 +8975,8 @@ private void computeQuickButtons () { } final String[] quickReactions = Settings.instance().getQuickReactions(tdlib); - for (int a = 0; a < quickReactions.length; a++) { + final boolean doubleTapOnly = Settings.instance().isQuickReactionDoubleTapEnabled() && quickReactions.length == 1; + for (int a = 0; !doubleTapOnly && a < quickReactions.length; a++) { final String reactionString = quickReactions[a]; TdApi.ReactionType reactionType = TD.toReactionType(reactionString); final boolean canReact = canSendReaction(reactionType); @@ -8945,16 +8986,8 @@ private void computeQuickButtons () { reactionDrawable.setComplexReceiver(currentComplexReceiver); final boolean isOdd = a % 2 == 1; - final SwipeQuickAction quickReaction = new SwipeQuickAction(reactionObj.getTitle(), reactionDrawable, () -> { - boolean hasReaction = messageReactions.hasReaction(reactionType); - if (Config.DISABLE_ANONYMOUS_NON_OWNER_REACTIONS && !hasReaction && tdlib.isAnonymousAdminNonCreator(msg.chatId)) { - showContentHint(findCurrentView(), null, R.string.error_ANONYMOUS_REACTIONS_DISABLED); - } else if (!Config.PROTECT_ANONYMOUS_REACTIONS || hasReaction || !canGetAddedReactions() || messagesController().callNonAnonymousProtection(getId() + reactionObj.hashCode(), null)) { - if (messageReactions.toggleReaction(reactionType, false, false, handler(findCurrentView(), null, () -> {}))) { - scheduleSetReactionAnimation(new NextReactionAnimation(reactionObj, NextReactionAnimation.TYPE_QUICK)); - } - } - }, false, true); + final SwipeQuickAction quickReaction = new SwipeQuickAction(reactionObj.getTitle(), reactionDrawable, + () -> performQuickReaction(findCurrentView(), reactionType, reactionObj), false, true); if (isOdd) { rightQuickDefaultPosition += 1; diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditEnabledReactionsController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditEnabledReactionsController.java index fb11b2e2ae..825a17a568 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EditEnabledReactionsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditEnabledReactionsController.java @@ -194,6 +194,11 @@ protected void setValuedSetting (ListItem item, SettingView v, boolean isUpdate) if (view != null) { view.setRadioEnabled(!quickReactions.isEmpty(), isUpdate); } + } else if (viewId == R.id.btn_quick_reaction_double_tap) { + TogglerView view = v.getToggler(); + if (view != null) { + view.setRadioEnabled(!quickReactions.isEmpty() && Settings.instance().isQuickReactionDoubleTapEnabled(), isUpdate); + } } } @@ -320,8 +325,10 @@ private void buildCells () { items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); } else if (type == TYPE_QUICK_REACTION) { items.add(toggleItem = new ListItem(ListItem.TYPE_RADIO_SETTING, R.id.btn_quick_reaction_enabled, 0, R.string.QuickReactionEnable, isToggleSelected())); + items.add(new ListItem(ListItem.TYPE_SEPARATOR_FULL)); + items.add(new ListItem(ListItem.TYPE_RADIO_SETTING, R.id.btn_quick_reaction_double_tap, 0, R.string.QuickReactionDoubleTap)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); - items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, Lang.getMarkdownString(this, R.string.QuickReactionEnableDesc), false)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, Lang.getMarkdownString(this, R.string.QuickReactionDoubleTapDesc), false)); items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); } @@ -455,11 +462,26 @@ public void onClick (View v) { quickReactions.add(tdlib.defaultEmojiReaction()); } else { quickReactions.clear(); + Settings.instance().setQuickReactionDoubleTapEnabled(false); } toggleItem.setSelected(isToggleSelected()); updateQuickReactionsSettings(); adapter.updateAllValuedSettingsById(R.id.btn_enabledReactionsCheckboxGroup); adapter.updateValuedSettingById(R.id.btn_quick_reaction_enabled); + adapter.updateValuedSettingById(R.id.btn_quick_reaction_double_tap); + } + + if (viewId == R.id.btn_quick_reaction_double_tap) { + if (quickReactions.isEmpty()) { + quickReactions.add(tdlib.defaultEmojiReaction()); + toggleItem.setSelected(true); + updateQuickReactionsSettings(); + adapter.updateAllValuedSettingsById(R.id.btn_enabledReactionsCheckboxGroup); + adapter.updateValuedSettingById(R.id.btn_quick_reaction_enabled); + } + Settings settings = Settings.instance(); + settings.setQuickReactionDoubleTapEnabled(!settings.isQuickReactionDoubleTapEnabled()); + adapter.updateValuedSettingById(R.id.btn_quick_reaction_double_tap); } if (v instanceof ReactionCheckboxSettingsView) { @@ -518,10 +540,15 @@ public void onClick (View v) { } updateQuickReactionsSettings(); + if (quickReactions.isEmpty()) { + Settings.instance().setQuickReactionDoubleTapEnabled(false); + } + toggleItem.setSelected(isToggleSelected()); adapter.updateAllValuedSettingsById(R.id.btn_enabledReactionsCheckboxGroup); adapter.updateValuedSettingById(R.id.btn_quick_reaction_enabled); + adapter.updateValuedSettingById(R.id.btn_quick_reaction_double_tap); break; } } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java b/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java index ac9eb6dca7..9ff570e852 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java @@ -4797,7 +4797,6 @@ private void showForumTopicOptions (TdApi.ForumTopic forumTopic, SettingsAdapter ((ForumTopicIconModifier) item.getDrawModifier()).setPinned(item.getIntValue() == newDefaultForumTopicId); } } - topicsAdapter.notifyDataSetChanged(); updateTopicBar(true); } else if (id == R.id.btn_editTopic) { promptEditForumTopic(forumTopic, topicsAdapter); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java index 0dc2f2cef3..556cc92106 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java @@ -2064,7 +2064,24 @@ public void onFocus () { @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && (requestCode == Intents.ACTIVITY_RESULT_RINGTONE || requestCode == Intents.ACTIVITY_RESULT_RINGTONE_NOTIFICATION)) { - final Uri originalUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (data == null) { + return; + } + Uri originalUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (originalUri == null && data.getData() != null) { + // Some vendor ringtone pickers delegate to DocumentsUI and return the selected + // audio file as Intent.data instead of EXTRA_RINGTONE_PICKED_URI. + originalUri = data.getData(); + } + if (originalUri == null && data.getClipData() != null && data.getClipData().getItemCount() > 0) { + originalUri = data.getClipData().getItemAt(0).getUri(); + } + if (originalUri == null) { + originalUri = data.getParcelableExtra(Intent.EXTRA_STREAM); + } + if (originalUri == null && !data.hasExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)) { + return; + } String ringtoneUri; String name; diff --git a/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java b/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java index 978ce1e134..791c41a317 100644 --- a/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java +++ b/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java @@ -357,6 +357,7 @@ public static String accountInfoPrefix (int accountId) { private static final String KEY_QUICK_REACTION = "quick_reaction"; private static final String KEY_QUICK_REACTIONS = "quick_reactions"; + private static final String KEY_QUICK_REACTION_DOUBLE_TAP = "quick_reaction_double_tap"; private static final String KEY_BIG_REACTIONS_IN_CHANNELS = "big_reactions_in_channels"; private static final String KEY_BIG_REACTIONS_IN_CHATS = "big_reactions_in_chats"; @@ -6872,6 +6873,14 @@ public String[] getQuickReactions (Tdlib tdlib) { return quickReactions; } + public void setQuickReactionDoubleTapEnabled (boolean enabled) { + putBoolean(KEY_QUICK_REACTION_DOUBLE_TAP, enabled); + } + + public boolean isQuickReactionDoubleTapEnabled () { + return getBoolean(KEY_QUICK_REACTION_DOUBLE_TAP, false); + } + public void setBigReactionsInChannels (boolean inChannels) { pmc.putBoolean(KEY_BIG_REACTIONS_IN_CHANNELS, inChannels); } diff --git a/app/src/main/res/values-ru/frogram_strings.xml b/app/src/main/res/values-ru/frogram_strings.xml index f3d4968c8f..f41861f7f9 100644 --- a/app/src/main/res/values-ru/frogram_strings.xml +++ b/app/src/main/res/values-ru/frogram_strings.xml @@ -41,4 +41,6 @@ Несохранённые изменения могут быть потеряны. Форматированная публикация Открыть публикацию полностью + Реакция по двойному нажатию + Дважды нажмите на сообщение, чтобы поставить первую выбранную ниже реакцию. Если выбрана одна реакция, реакции по свайпу отключаются. В некоторых группах и каналах отдельные реакции могут быть недоступны. diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 3f39637220..193e449468 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -1125,6 +1125,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e6c0e3230..63c65d129e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2066,6 +2066,8 @@ Enable Quick Reaction Some groups and channels may not allow specific reactions. + Reaction on double tap + Double-tap a message to apply the first reaction selected below. When only one reaction is selected, swipe reactions are disabled. Some groups and channels may not allow specific reactions. Allow members to react to group messages %1$s reaction