From a755645d28671f41b55b7f5c6a46fe8a3b9e2130 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:33:38 +0900 Subject: [PATCH 01/23] Fixed duplicate sticker / emoji packs during search --- .../challegram/ui/StickersTrendingController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/org/thunderdog/challegram/ui/StickersTrendingController.java b/app/src/main/java/org/thunderdog/challegram/ui/StickersTrendingController.java index f2343b26a3..6b7da2457e 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/StickersTrendingController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/StickersTrendingController.java @@ -49,6 +49,7 @@ import me.vkryl.core.StringUtils; import me.vkryl.core.collection.LongSet; import me.vkryl.core.lambda.CancellableRunnable; +import me.vkryl.core.lambda.RunnableData; public class StickersTrendingController extends ViewController implements StickerSmallView.StickerMovementCallback, Client.ResultHandler, TGStickerObj.DataProvider, StickersListener, TGStickerSetInfo.ViewCallback { private final boolean isEmoji; @@ -269,6 +270,13 @@ private void loadTrending (int offset, int limit, int cellCount) { return; } + // TODO: rework properly to tdlib.ui().getEmojiStickers(..) + + if (offset > 0) { + handler.onResult(new TdApi.StickerSets(0, new TdApi.StickerSetInfo[0])); + return; + } + tdlib.send(new TdApi.SearchInstalledStickerSets(stickerType, searchRequest, 200), (foundInstalledStickerSets, error) -> { if (handler.isCancelled()) return; @@ -535,6 +543,7 @@ public void processResult (TdApi.Object result) { } case TdApi.Error.CONSTRUCTOR: { UI.showError(result); + processResultImpl(new TdApi.StickerSetInfo[0]); break; } } From 87ebe2b2e66e4d1a66bce11112b818b3b08beb76 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:17:08 +0900 Subject: [PATCH 02/23] Fixed crash when cancelling video recording with animations disabled --- .../player/RecordAudioVideoController.java | 17 +++++++++++++++++ vkryl/android | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java b/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java index 32bf929448..709c1e09b7 100644 --- a/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java +++ b/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java @@ -1075,13 +1075,21 @@ private boolean awaitingRoundResult () { return roundCloseMode != CLOSE_MODE_CANCEL; } + private Throwable releasedTrace; + private boolean cleanupVideoPending; + private void cleanupVideoRecording () { if (recordingVideo && Math.max(recordFactor, editFactor) * (1f - renderFactor) == 0f && ownedCamera != null && !awaitingRoundResult()) { + if (recordingRoundVideo) { + cleanupVideoPending = true; + return; + } ownedCamera.onCleanAfterHide(); ownedCamera.releaseCameraLayout(); setupCamera(false); context.releaseCameraOwnership(); + releasedTrace = Log.generateException(); ownedCamera = null; resetRoundState(); @@ -1211,6 +1219,7 @@ private void onRecordFocus () { } private void onRecordRemoved () { + // note: when animations disabled happens before finishVideoRecording context.setScreenFlagEnabled(BaseActivity.SCREEN_FLAG_RECORDING, false); context.setOrientationLockFlagEnabled(BaseActivity.ORIENTATION_FLAG_RECORDING, false); cleanupVideoRecording(); @@ -1457,12 +1466,20 @@ private void startVideoRecording () { } private void finishVideoRecording (int closeMode) { + // note: when animations disabled, happens after cleanupVideoRecording is called if (!this.recordingRoundVideo) throw new IllegalStateException(); this.recordingRoundVideo = false; this.roundCloseMode = closeMode; final boolean needResult = closeMode != CLOSE_MODE_CANCEL; + if (ownedCamera == null) { + throw new RuntimeException(releasedTrace); + } ownedCamera.getLegacyManager().finishOrCancelRoundVideoCapture(roundKey, needResult); + if (cleanupVideoPending) { + cleanupVideoPending = false; + cleanupVideoRecording(); + } } @Override diff --git a/vkryl/android b/vkryl/android index 8aaa0b8246..5723143b71 160000 --- a/vkryl/android +++ b/vkryl/android @@ -1 +1 @@ -Subproject commit 8aaa0b82463d5f6c4c09da2e9f0be4bd57e97878 +Subproject commit 5723143b710b0e85b4930e023a3ff9ac8d01a0bc From 3597dea49abd0ada35b192dcc5aaf71c384317c5 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:28:41 +0900 Subject: [PATCH 03/23] Upgraded TDLib to tdlib/td@1a50ec4 --- tdlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdlib b/tdlib index 5c90370975..ea5e330d37 160000 --- a/tdlib +++ b/tdlib @@ -1 +1 @@ -Subproject commit 5c9037097503a6766e2233fa86aa0a4d95051eef +Subproject commit ea5e330d37ed6e01c7ccbb7fdb801913397e76ea From 8d3f85d38e4352978a061751844b6a7973d8ab9c Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:35:08 +0900 Subject: [PATCH 04/23] Fixed crash in `TdlibMessageViewer` when mutable `Message` is passed --- .../challegram/telegram/TdlibMessageViewer.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibMessageViewer.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibMessageViewer.java index 84881ab11f..cebb33f583 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibMessageViewer.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibMessageViewer.java @@ -266,7 +266,7 @@ public VisibleMessage removeVisibleMessage (long chatId, long messageId) { assertChat(chatId); VisibleMessage visibleMessage = find(messageId); if (visibleMessage != null && visibleMessage.visibility.markAsHidden()) { - trackMessage(visibleMessage, false); + trackMessage(visibleMessage, false, messageId); return visibleMessage; } return null; @@ -306,6 +306,10 @@ public boolean isEmpty () { private void trackMessage (VisibleMessage visibleMessage, boolean isVisible) { final Long messageId = visibleMessage.getMessageId(); + trackMessage(visibleMessage, isVisible, messageId); + } + + private void trackMessage (VisibleMessage visibleMessage, boolean isVisible, Long messageId) { if (isVisible) { if (!state.visibleMessageIds.add(messageId)) throw new IllegalStateException(); @@ -313,15 +317,15 @@ private void trackMessage (VisibleMessage visibleMessage, boolean isVisible) { if (!state.visibleProtectedMessageIds.add(messageId)) throw new IllegalStateException(); } - visibleMessages.put(visibleMessage.getMessageId(), visibleMessage); - if (visibleMessage.needRefreshInteractionInfo() && state.refreshMessageIds.add(visibleMessage.getMessageId())) { + visibleMessages.put(messageId, visibleMessage); + if (visibleMessage.needRefreshInteractionInfo() && state.refreshMessageIds.add(messageId)) { checkRefreshInteractionInfo(); } } else { if (!state.visibleMessageIds.remove(messageId)) throw new IllegalStateException(); state.visibleProtectedMessageIds.remove(messageId); - visibleMessages.remove(visibleMessage.getMessageId()); + visibleMessages.remove(messageId); viewport.trackRecentlyViewedMessage(this, visibleMessage); if (state.refreshMessageIds.remove(messageId)) { checkRefreshInteractionInfo(); From 16fa6ea10b86f6f1f5a676d6f4b5ac6c61d83c55 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:43:15 +0900 Subject: [PATCH 05/23] Fixed displaying certain built-in emoji in emoji-only view --- .../challegram/data/TGMessageSticker.java | 10 +++++----- .../thunderdog/challegram/emoji/EmojiSpan.java | 3 +++ .../challegram/emoji/EmojiSpanImpl.java | 6 ++++++ .../challegram/util/NonBubbleEmojiLayout.java | 15 +++++++++------ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGMessageSticker.java b/app/src/main/java/org/thunderdog/challegram/data/TGMessageSticker.java index e4bd5bce88..6ff5a27737 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGMessageSticker.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessageSticker.java @@ -91,13 +91,13 @@ private class Representation { public boolean needThemedColorFilter; public Representation (@NonNull TdApi.Sticker sticker, int fitzpatrickType, boolean allowNoLoop, boolean forcePlayOnce) { - this(sticker.id, sticker.emoji, sticker, fitzpatrickType, allowNoLoop, forcePlayOnce); + this(sticker.id, sticker.emoji, Emoji.instance().getEmojiInfo(sticker.emoji), sticker, fitzpatrickType, allowNoLoop, forcePlayOnce); } - public Representation (long stickerId, String emoji, @Nullable TdApi.Sticker sticker, int fitzpatrickType, boolean allowNoLoop, boolean forcePlayOnce) { + public Representation (long stickerId, String emoji, @Nullable EmojiInfo info, @Nullable TdApi.Sticker sticker, int fitzpatrickType, boolean allowNoLoop, boolean forcePlayOnce) { this.stickerId = stickerId; this.emoji = emoji; - this.emojiInfo = stickerId == 0 ? Emoji.instance().getEmojiInfo(emoji) : null; + this.emojiInfo = stickerId == 0 ? info : null; setSticker(sticker, fitzpatrickType, allowNoLoop, forcePlayOnce); } @@ -452,11 +452,11 @@ private void setStickers (TdApi.FormattedText text) { this.representation = new ArrayList<>(); this.multiEmojiLayout = NonBubbleEmojiLayout.create(text); if (multiEmojiLayout != null) { - for (NonBubbleEmojiLayout.Item emojiR: multiEmojiLayout.items) { + for (NonBubbleEmojiLayout.Item emojiR : multiEmojiLayout.items) { if (emojiR.type == NonBubbleEmojiLayout.Item.EMOJI) { TdlibEmojiManager.Entry entry = emojiR.customEmojiId != 0 ? tdlib.emoji().findOrPostponeRequest(emojiR.customEmojiId, this) : null; - representation.add(new Representation(emojiR.customEmojiId, emojiR.emoji, entry != null ? entry.value : null, 0, true, false)); + representation.add(new Representation(emojiR.customEmojiId, emojiR.emoji, emojiR.info, entry != null ? entry.value : null, 0, true, false)); } } tdlib.emoji().performPostponedRequests(); diff --git a/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpan.java b/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpan.java index 7abeadd5da..559c990d07 100644 --- a/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpan.java +++ b/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpan.java @@ -28,6 +28,9 @@ public interface EmojiSpan { default long getCustomEmojiId () { return 0; } + default EmojiInfo getBuiltInEmojiInfo () { + return null; + } default boolean belongsToSurface (CustomEmojiSurfaceProvider customEmojiSurfaceProvider) { return false; } diff --git a/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpanImpl.java b/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpanImpl.java index b22409315f..4647fddbe4 100644 --- a/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpanImpl.java +++ b/app/src/main/java/org/thunderdog/challegram/emoji/EmojiSpanImpl.java @@ -51,6 +51,12 @@ protected EmojiSpanImpl (@Nullable EmojiInfo info) { this.info = info; } + + @Override + public EmojiInfo getBuiltInEmojiInfo () { + return info; + } + @Override public final EmojiSpan toBuiltInEmojiSpan () { return info != null ? newSpan(info) : null; diff --git a/app/src/main/java/org/thunderdog/challegram/util/NonBubbleEmojiLayout.java b/app/src/main/java/org/thunderdog/challegram/util/NonBubbleEmojiLayout.java index f3e4f1c101..fc3a47a634 100644 --- a/app/src/main/java/org/thunderdog/challegram/util/NonBubbleEmojiLayout.java +++ b/app/src/main/java/org/thunderdog/challegram/util/NonBubbleEmojiLayout.java @@ -20,6 +20,7 @@ import org.drinkless.tdlib.TdApi; import org.thunderdog.challegram.emoji.Emoji; +import org.thunderdog.challegram.emoji.EmojiInfo; import org.thunderdog.challegram.emoji.EmojiSpan; import java.util.ArrayList; @@ -39,8 +40,8 @@ public static NonBubbleEmojiLayout create (TdApi.FormattedText text) { } } - private void addSpan (String emoji, long customEmojiId) { - items.add(new Item(Item.EMOJI, emoji, customEmojiId)); + private void addSpan (String emoji, long customEmojiId, @Nullable EmojiInfo info) { + items.add(new Item(Item.EMOJI, emoji, customEmojiId, info)); } private void addRow () { @@ -78,7 +79,7 @@ private static boolean isValidEmojiText (TdApi.FormattedText formattedText, @Nul } index = end; if (layout != null) { - layout.addSpan(formattedText.text.substring(start, end), ((TdApi.TextEntityTypeCustomEmoji) entity.type).customEmojiId); + layout.addSpan(formattedText.text.substring(start, end), ((TdApi.TextEntityTypeCustomEmoji) entity.type).customEmojiId, null); } continue; } @@ -122,7 +123,7 @@ private static boolean isValidTextBlock (String text, @Nullable NonBubbleEmojiLa } spanIsFound = true; if (layout != null) { - layout.addSpan(text.substring(start, end), span.getCustomEmojiId()); + layout.addSpan(text.substring(start, end), span.getCustomEmojiId(), span.getBuiltInEmojiInfo()); } } if (!spanIsFound) { @@ -231,15 +232,17 @@ public static class Item { public final String emoji; public final long customEmojiId; public final int type; + public final EmojiInfo info; private Item (int type) { - this(type, null, 0); + this(type, null, 0, null); } - private Item (int type, String emoji, long customEmojiId) { + private Item (int type, String emoji, long customEmojiId, EmojiInfo info) { this.type = type; this.emoji = emoji; this.customEmojiId = customEmojiId; + this.info = info; } } } From 9c4fce6b410c96bf326364894a71418df747be12 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Fri, 22 Dec 2023 17:15:24 +0900 Subject: [PATCH 06/23] Fixed removing cells in blocked list after successfull unblock --- .../challegram/ui/SettingsBlockedController.java | 13 ++++++++++++- vkryl/td | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java index 5871cabc2b..c374f47a6e 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java @@ -133,7 +133,11 @@ public void onFocus () { public void unblockSender (TGUser user) { showOptions(Lang.getStringBold(R.string.QUnblockX, tdlib.senderName(user.getSenderId())), new int[]{R.id.btn_unblockSender, R.id.btn_cancel}, new String[]{Lang.getString(R.string.Unblock), Lang.getString(R.string.Cancel)}, new int[]{OPTION_COLOR_RED, OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_block_24, R.drawable.baseline_cancel_24}, (itemView, id) -> { if (id == R.id.btn_unblockSender) { - tdlib.blockSender(user.getSenderId(), null, tdlib.okHandler()); + tdlib.blockSender(user.getSenderId(), null, tdlib.okHandler(() -> { + runOnUiThreadOptional(() -> { + removeSender(user.getSenderId()); + }); + })); } return true; }); @@ -338,6 +342,13 @@ private void addSender (TdApi.MessageSender sender) { } } + private void removeSender (TdApi.MessageSender sender) { + int index = indexOfSender(ChatId.fromSender(sender)); + if (index != -1) { + removeSender(index); + } + } + private void removeSender (int position) { if (senders.size() == 1) { senders.clear(); diff --git a/vkryl/td b/vkryl/td index 2cf297d692..8aecd92f14 160000 --- a/vkryl/td +++ b/vkryl/td @@ -1 +1 @@ -Subproject commit 2cf297d69260f04ad03420032864d47e03f61454 +Subproject commit 8aecd92f14647d2ed4bb6f7b567352db0d61de67 From c7c7f1bf854e9e15d10777d5308cd9e75545b8c1 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Fri, 22 Dec 2023 19:50:23 +0900 Subject: [PATCH 07/23] Rename `subscribeForAnyUpdates` to `subscribeForGlobalUpdates` for clarity --- .../component/chat/TdlibSingleUnreadReactionsManager.java | 4 ++-- .../org/thunderdog/challegram/telegram/SenderListManager.java | 4 ++-- .../org/thunderdog/challegram/telegram/TdlibListeners.java | 4 ++-- .../java/org/thunderdog/challegram/ui/CallListController.java | 4 ++-- .../thunderdog/challegram/ui/ChatLinkMembersController.java | 4 ++-- .../java/org/thunderdog/challegram/ui/ChatsController.java | 4 ++-- .../thunderdog/challegram/ui/EmojiMediaListController.java | 2 +- .../thunderdog/challegram/ui/SettingsBlockedController.java | 4 ++-- .../thunderdog/challegram/ui/SettingsPrivacyController.java | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/TdlibSingleUnreadReactionsManager.java b/app/src/main/java/org/thunderdog/challegram/component/chat/TdlibSingleUnreadReactionsManager.java index f3a9340952..0e9eb99e1d 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/TdlibSingleUnreadReactionsManager.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/TdlibSingleUnreadReactionsManager.java @@ -39,12 +39,12 @@ public class TdlibSingleUnreadReactionsManager implements ChatListener, MessageL public TdlibSingleUnreadReactionsManager (Tdlib tdlib) { this.tdlib = tdlib; - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); } @Override public void performDestroy () { - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); } @UiThread diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java b/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java index dc03b046e8..59eedbcd26 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java @@ -45,13 +45,13 @@ public SenderListManager (Tdlib tdlib, int initialLoadCount, int loadCount, @Nul @Override protected void subscribeToUpdates () { tdlib.cache().subscribeToAnyUpdates(this); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); } @Override protected void unsubscribeFromUpdates () { tdlib.cache().unsubscribeFromAnyUpdates(this); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); } @Override diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java index a2d6989279..849348d741 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java @@ -134,7 +134,7 @@ public void unsubscribeFromUpdates (TdApi.Message message) { } @AnyThread - public void subscribeForAnyUpdates (Object any) { + public void subscribeForGlobalUpdates (Object any) { synchronized (this) { if (any instanceof MessageListener) { messageListeners.add((MessageListener) any); @@ -185,7 +185,7 @@ public void subscribeForAnyUpdates (Object any) { } @AnyThread - public void unsubscribeFromAnyUpdates (Object any) { + public void unsubscribeFromGlobalUpdates (Object any) { synchronized (this) { if (any instanceof MessageListener) { messageListeners.remove((MessageListener) any); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/CallListController.java b/app/src/main/java/org/thunderdog/challegram/ui/CallListController.java index 54fe3f57c6..8e54a5353a 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/CallListController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/CallListController.java @@ -166,7 +166,7 @@ public void onScrolled (@NonNull RecyclerView recyclerView, int dx, int dy) { tdlib.client().send(new TdApi.SearchCallMessages(null, Screen.calculateLoadingItems(Screen.dp(72f), 20), false), this); tdlib.client().send(new TdApi.GetTopChats(new TdApi.TopChatCategoryCalls(), 30), this); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); tdlib.context().dateManager().addListener(this); } @@ -596,7 +596,7 @@ private void buildSections () { @Override public void destroy () { super.destroy(); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); tdlib.context().dateManager().removeListener(this); } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/ChatLinkMembersController.java b/app/src/main/java/org/thunderdog/challegram/ui/ChatLinkMembersController.java index 9904250060..aac40d9b78 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/ChatLinkMembersController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/ChatLinkMembersController.java @@ -178,7 +178,7 @@ public void onScrolled (RecyclerView recyclerView, int dx, int dy) { } }); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); } private void openRightsScreen (long userId) { @@ -201,7 +201,7 @@ private void openRightsScreen (long userId) { @Override public void destroy () { super.destroy(); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); } @Override diff --git a/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java b/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java index ddd2e7660b..eb392d2243 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java @@ -540,7 +540,7 @@ public void onSwiped (@NonNull RecyclerView.ViewHolder viewHolder, int direction updateNetworkStatus(tdlib.connectionState()); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); tdlib.cache().subscribeToAnyUpdates(this); Settings.instance().addChatListModeListener(this); @@ -2570,7 +2570,7 @@ public void destroy () { } Settings.instance().removeChatListModeListener(this); tdlib.settings().removeUserPreferenceChangeListener(this); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); tdlib.cache().unsubscribeFromAnyUpdates(this); list.unsubscribeFromUpdates(this); TGLegacyManager.instance().removeEmojiListener(this); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EmojiMediaListController.java b/app/src/main/java/org/thunderdog/challegram/ui/EmojiMediaListController.java index e3fb97a365..50ee8d423a 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EmojiMediaListController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EmojiMediaListController.java @@ -571,7 +571,7 @@ public void onScrolled (@NonNull RecyclerView recyclerView, int dx, int dy) { @Override public void destroy () { super.destroy(); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); stickersController.destroy(); trendingSetsController.destroy(); } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java index c374f47a6e..f141cc0204 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java @@ -265,14 +265,14 @@ public void onScrolled (RecyclerView recyclerView, int dx, int dy) { } }); tdlib.cache().addGlobalUsersListener(this); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); } @Override public void destroy () { super.destroy(); tdlib.cache().removeGlobalUsersListener(this); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); } private void buildCells () { diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java index df6359b5a1..c016b04722 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java @@ -241,7 +241,7 @@ public void setValuedSetting (ListItem item, SettingView v, boolean isUpdate) { tdlib.cache().putGlobalUserDataListener(this); tdlib.contacts().addStatusListener(this); - tdlib.listeners().subscribeForAnyUpdates(this); + tdlib.listeners().subscribeForGlobalUpdates(this); } @Override @@ -272,7 +272,7 @@ public void destroy () { super.destroy(); tdlib.cache().deleteGlobalUserDataListener(this); tdlib.contacts().removeStatusListener(this); - tdlib.listeners().unsubscribeFromAnyUpdates(this); + tdlib.listeners().unsubscribeFromGlobalUpdates(this); } @Override From 2f4778e2b1b9ff75dd990cc6f54b6835d047699a Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Fri, 22 Dec 2023 20:02:28 +0900 Subject: [PATCH 08/23] Rename `subscribeToAnyUpdates` to `subscribeForGlobalUpdates` for clarity --- .../telegram/SenderListManager.java | 4 +- .../challegram/telegram/TdlibCache.java | 52 ++++---- .../challegram/telegram/TdlibListeners.java | 124 +++++++++--------- .../challegram/ui/ChatsController.java | 4 +- .../challegram/ui/SharedChatsController.java | 4 +- 5 files changed, 94 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java b/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java index 59eedbcd26..f842acda4b 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/SenderListManager.java @@ -44,13 +44,13 @@ public SenderListManager (Tdlib tdlib, int initialLoadCount, int loadCount, @Nul @Override protected void subscribeToUpdates () { - tdlib.cache().subscribeToAnyUpdates(this); + tdlib.cache().subscribeForGlobalUpdates(this); tdlib.listeners().subscribeForGlobalUpdates(this); } @Override protected void unsubscribeFromUpdates () { - tdlib.cache().unsubscribeFromAnyUpdates(this); + tdlib.cache().unsubscribeFromGlobalUpdates(this); tdlib.listeners().unsubscribeFromGlobalUpdates(this); } diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java index a35bb6f050..848d2d8a4c 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java @@ -728,45 +728,45 @@ public void onUpdateCallSettings (int callId, CallSettings settings) { // Listeners - public void subscribeToAnyUpdates (Object any) { - if (any instanceof UserDataChangeListener) { - __putGlobalUserDataListener((UserDataChangeListener) any); + public void subscribeForGlobalUpdates (Object globalListener) { + if (globalListener instanceof UserDataChangeListener) { + __putGlobalUserDataListener((UserDataChangeListener) globalListener); } - if (any instanceof UserStatusChangeListener) { - __putGlobalStatusListener((UserStatusChangeListener) any); + if (globalListener instanceof UserStatusChangeListener) { + __putGlobalStatusListener((UserStatusChangeListener) globalListener); } - if (any instanceof BasicGroupDataChangeListener) { - putGlobalBasicGroupListener((BasicGroupDataChangeListener) any); + if (globalListener instanceof BasicGroupDataChangeListener) { + putGlobalBasicGroupListener((BasicGroupDataChangeListener) globalListener); } - if (any instanceof SupergroupDataChangeListener) { - putGlobalSupergroupListener((SupergroupDataChangeListener) any); + if (globalListener instanceof SupergroupDataChangeListener) { + putGlobalSupergroupListener((SupergroupDataChangeListener) globalListener); } - if (any instanceof SecretChatDataChangeListener) { - putGlobalSecretChatListener((SecretChatDataChangeListener) any); + if (globalListener instanceof SecretChatDataChangeListener) { + putGlobalSecretChatListener((SecretChatDataChangeListener) globalListener); } - if (any instanceof CallStateChangeListener) { - putGlobalCallListener((CallStateChangeListener) any); + if (globalListener instanceof CallStateChangeListener) { + putGlobalCallListener((CallStateChangeListener) globalListener); } } - public void unsubscribeFromAnyUpdates (Object any) { - if (any instanceof UserDataChangeListener) { - __deleteGlobalUserDataListener((UserDataChangeListener) any); + public void unsubscribeFromGlobalUpdates (Object globalListener) { + if (globalListener instanceof UserDataChangeListener) { + __deleteGlobalUserDataListener((UserDataChangeListener) globalListener); } - if (any instanceof UserStatusChangeListener) { - __deleteGlobalStatusListener((UserStatusChangeListener) any); + if (globalListener instanceof UserStatusChangeListener) { + __deleteGlobalStatusListener((UserStatusChangeListener) globalListener); } - if (any instanceof BasicGroupDataChangeListener) { - deleteGlobalBasicGroupListener((BasicGroupDataChangeListener) any); + if (globalListener instanceof BasicGroupDataChangeListener) { + deleteGlobalBasicGroupListener((BasicGroupDataChangeListener) globalListener); } - if (any instanceof SupergroupDataChangeListener) { - deleteGlobalSupergroupListener((SupergroupDataChangeListener) any); + if (globalListener instanceof SupergroupDataChangeListener) { + deleteGlobalSupergroupListener((SupergroupDataChangeListener) globalListener); } - if (any instanceof SecretChatDataChangeListener) { - deleteGlobalSecretChatListener((SecretChatDataChangeListener) any); + if (globalListener instanceof SecretChatDataChangeListener) { + deleteGlobalSecretChatListener((SecretChatDataChangeListener) globalListener); } - if (any instanceof CallStateChangeListener) { - deleteGlobalCallListener((CallStateChangeListener) any); + if (globalListener instanceof CallStateChangeListener) { + deleteGlobalCallListener((CallStateChangeListener) globalListener); } } diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java index 849348d741..3e32c9cefb 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibListeners.java @@ -134,103 +134,103 @@ public void unsubscribeFromUpdates (TdApi.Message message) { } @AnyThread - public void subscribeForGlobalUpdates (Object any) { + public void subscribeForGlobalUpdates (Object globalListener) { synchronized (this) { - if (any instanceof MessageListener) { - messageListeners.add((MessageListener) any); + if (globalListener instanceof MessageListener) { + messageListeners.add((MessageListener) globalListener); } - if (any instanceof MessageEditListener) { - messageEditListeners.add((MessageEditListener) any); + if (globalListener instanceof MessageEditListener) { + messageEditListeners.add((MessageEditListener) globalListener); } - if (any instanceof ChatListener) { - chatListeners.add((ChatListener) any); + if (globalListener instanceof ChatListener) { + chatListeners.add((ChatListener) globalListener); } - if (any instanceof NotificationSettingsListener) { - settingsListeners.add((NotificationSettingsListener) any); + if (globalListener instanceof NotificationSettingsListener) { + settingsListeners.add((NotificationSettingsListener) globalListener); } - if (any instanceof StickersListener) { - stickersListeners.add((StickersListener) any); + if (globalListener instanceof StickersListener) { + stickersListeners.add((StickersListener) globalListener); } - if (any instanceof AnimationsListener) { - animationsListeners.add((AnimationsListener) any); + if (globalListener instanceof AnimationsListener) { + animationsListeners.add((AnimationsListener) globalListener); } - if (any instanceof ConnectionListener) { - connectionListeners.add((ConnectionListener) any); + if (globalListener instanceof ConnectionListener) { + connectionListeners.add((ConnectionListener) globalListener); } - if (any instanceof TdlibOptionListener) { - optionListeners.add((TdlibOptionListener) any); + if (globalListener instanceof TdlibOptionListener) { + optionListeners.add((TdlibOptionListener) globalListener); } - if (any instanceof CounterChangeListener) { - totalCountersListeners.add((CounterChangeListener) any); + if (globalListener instanceof CounterChangeListener) { + totalCountersListeners.add((CounterChangeListener) globalListener); } - if (any instanceof ChatsNearbyListener) { - chatsNearbyListeners.add((ChatsNearbyListener) any); + if (globalListener instanceof ChatsNearbyListener) { + chatsNearbyListeners.add((ChatsNearbyListener) globalListener); } - if (any instanceof AnimatedEmojiListener) { - animatedEmojiListeners.add((AnimatedEmojiListener) any); + if (globalListener instanceof AnimatedEmojiListener) { + animatedEmojiListeners.add((AnimatedEmojiListener) globalListener); } - if (any instanceof PrivacySettingsListener) { - privacySettingsListeners.add((PrivacySettingsListener) any); + if (globalListener instanceof PrivacySettingsListener) { + privacySettingsListeners.add((PrivacySettingsListener) globalListener); } - if (any instanceof PrivateCallListener) { - privateCallListeners.add((PrivateCallListener) any); + if (globalListener instanceof PrivateCallListener) { + privateCallListeners.add((PrivateCallListener) globalListener); } - if (any instanceof GroupCallListener) { - groupCallListeners.add((GroupCallListener) any); + if (globalListener instanceof GroupCallListener) { + groupCallListeners.add((GroupCallListener) globalListener); } - if (any instanceof SessionListener) { - sessionListeners.add((SessionListener) any); + if (globalListener instanceof SessionListener) { + sessionListeners.add((SessionListener) globalListener); } } } @AnyThread - public void unsubscribeFromGlobalUpdates (Object any) { + public void unsubscribeFromGlobalUpdates (Object globalListener) { synchronized (this) { - if (any instanceof MessageListener) { - messageListeners.remove((MessageListener) any); + if (globalListener instanceof MessageListener) { + messageListeners.remove((MessageListener) globalListener); } - if (any instanceof MessageEditListener) { - messageEditListeners.remove((MessageEditListener) any); + if (globalListener instanceof MessageEditListener) { + messageEditListeners.remove((MessageEditListener) globalListener); } - if (any instanceof ChatListener) { - chatListeners.remove((ChatListener) any); + if (globalListener instanceof ChatListener) { + chatListeners.remove((ChatListener) globalListener); } - if (any instanceof NotificationSettingsListener) { - settingsListeners.remove((NotificationSettingsListener) any); + if (globalListener instanceof NotificationSettingsListener) { + settingsListeners.remove((NotificationSettingsListener) globalListener); } - if (any instanceof StickersListener) { - stickersListeners.remove((StickersListener) any); + if (globalListener instanceof StickersListener) { + stickersListeners.remove((StickersListener) globalListener); } - if (any instanceof StickersListener) { - animationsListeners.remove((AnimationsListener) any); + if (globalListener instanceof StickersListener) { + animationsListeners.remove((AnimationsListener) globalListener); } - if (any instanceof ConnectionListener) { - connectionListeners.remove((ConnectionListener) any); + if (globalListener instanceof ConnectionListener) { + connectionListeners.remove((ConnectionListener) globalListener); } - if (any instanceof TdlibOptionListener) { - optionListeners.remove((TdlibOptionListener) any); + if (globalListener instanceof TdlibOptionListener) { + optionListeners.remove((TdlibOptionListener) globalListener); } - if (any instanceof CounterChangeListener) { - totalCountersListeners.remove((CounterChangeListener) any); + if (globalListener instanceof CounterChangeListener) { + totalCountersListeners.remove((CounterChangeListener) globalListener); } - if (any instanceof ChatsNearbyListener) { - chatsNearbyListeners.remove((ChatsNearbyListener) any); + if (globalListener instanceof ChatsNearbyListener) { + chatsNearbyListeners.remove((ChatsNearbyListener) globalListener); } - if (any instanceof AnimatedEmojiListener) { - animatedEmojiListeners.remove((AnimatedEmojiListener) any); + if (globalListener instanceof AnimatedEmojiListener) { + animatedEmojiListeners.remove((AnimatedEmojiListener) globalListener); } - if (any instanceof PrivacySettingsListener) { - privacySettingsListeners.remove((PrivacySettingsListener) any); + if (globalListener instanceof PrivacySettingsListener) { + privacySettingsListeners.remove((PrivacySettingsListener) globalListener); } - if (any instanceof PrivateCallListener) { - privateCallListeners.remove((PrivateCallListener) any); + if (globalListener instanceof PrivateCallListener) { + privateCallListeners.remove((PrivateCallListener) globalListener); } - if (any instanceof GroupCallListener) { - groupCallListeners.remove((GroupCallListener) any); + if (globalListener instanceof GroupCallListener) { + groupCallListeners.remove((GroupCallListener) globalListener); } - if (any instanceof SessionListener) { - sessionListeners.remove((SessionListener) any); + if (globalListener instanceof SessionListener) { + sessionListeners.remove((SessionListener) globalListener); } } } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java b/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java index eb392d2243..2002ac1fc0 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/ChatsController.java @@ -541,7 +541,7 @@ public void onSwiped (@NonNull RecyclerView.ViewHolder viewHolder, int direction updateNetworkStatus(tdlib.connectionState()); tdlib.listeners().subscribeForGlobalUpdates(this); - tdlib.cache().subscribeToAnyUpdates(this); + tdlib.cache().subscribeForGlobalUpdates(this); Settings.instance().addChatListModeListener(this); TGLegacyManager.instance().addEmojiListener(this); @@ -2571,7 +2571,7 @@ public void destroy () { Settings.instance().removeChatListModeListener(this); tdlib.settings().removeUserPreferenceChangeListener(this); tdlib.listeners().unsubscribeFromGlobalUpdates(this); - tdlib.cache().unsubscribeFromAnyUpdates(this); + tdlib.cache().unsubscribeFromGlobalUpdates(this); list.unsubscribeFromUpdates(this); TGLegacyManager.instance().removeEmojiListener(this); tdlib.contacts().removeListener(this); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SharedChatsController.java b/app/src/main/java/org/thunderdog/challegram/ui/SharedChatsController.java index 2f338ff012..183c4781c9 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SharedChatsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SharedChatsController.java @@ -129,13 +129,13 @@ protected boolean probablyHasEmoji () { @Override protected void onCreateView (Context context, MediaRecyclerView recyclerView, SettingsAdapter adapter) { super.onCreateView(context, recyclerView, adapter); - tdlib.cache().subscribeToAnyUpdates(this); + tdlib.cache().subscribeForGlobalUpdates(this); } @Override public void destroy () { super.destroy(); - tdlib.cache().unsubscribeFromAnyUpdates(this); + tdlib.cache().unsubscribeFromGlobalUpdates(this); } // Updates for texts From 723b6b4e675054aeab9a5d7e9b69ba8fb4a90b9d Mon Sep 17 00:00:00 2001 From: Arseny271 <56611696+Arseny271@users.noreply.github.com> Date: Sat, 23 Dec 2023 08:54:09 +0100 Subject: [PATCH 09/23] Implemented #494 #495 #496 #497 #498 #499 (PR #504) * Restricted and Unsupported messages * Edit rights * Media-only chats * Slow mode todo: fix colors for counter * Ability to set public profile photo (without picker and editor) * Avatar Editor * Merge Fixes * Avatar Picker * Messages Filter - basic logic * Message Filter Update * Icon fixes + edit rights fixes * Some slowmode fixes * Mirror Horizontal * Icon fixes in avatar picker + some right fixes * Some picker and editor improvements * Flip improvements * Channel Filter * Update TdlibUi.java * Update Rights editor * Rights icon fix * Disable videos in avatar picker * Update TdlibUi.java * Update * Add Slow mode timer for round messages * Hide messages filter * Fix for short round videos? * Revert messages filter * Revert messages filter * Hide "send as" photo when slow mode active * Hide "send as" photo when slow mode active * Update EGLEditorView.java * Remove hasWritePermission from almost all places * Update MessagesController.java * Improved animations * Update Animations * Update EditHeaderView.java * Update MediaViewController.java * Merge fix * Fix chatlist in SharedController * ShareControllerFix * Update Tdlib.java * Update Tdlib.java * Fixed crash when filtering reaction senders * Dismiss andim bugfix * Emoji status fix https://t.me/tgandroidtests/260056 * Temporary fix for sticker pack window twitching https://t.me/tgandroidtests/259942 * Fix reactions position in ChatList * Edit date fix for combined messages * Hide reactions preview in chat view when draft is visible * Update TGMessageSticker.java https://t.me/tgandroidtests/260461 * Update app/src/main/java/org/thunderdog/challegram/data/TGMessageSticker.java * Update app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java Co-authored-by: Vyacheslav <6242627+vkryl@users.noreply.github.com> * Update app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java Co-authored-by: Vyacheslav <6242627+vkryl@users.noreply.github.com> * Update app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java Co-authored-by: Vyacheslav <6242627+vkryl@users.noreply.github.com> * Update app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java Co-authored-by: Vyacheslav <6242627+vkryl@users.noreply.github.com> * Update SettingsPrivacyKeyController.java * Fix double opening for avatar picker * Use pluralBold instead plural in getSlowModeRestrictionText * Updating timer value in slow mode restriction hint --------- Co-authored-by: Vyacheslav <6242627+vkryl@users.noreply.github.com> --- .../thunderdog/challegram/BaseActivity.java | 16 +- .../attach/MediaBottomGalleryController.java | 26 +- .../component/attach/MediaGalleryAdapter.java | 16 +- .../attach/MediaGalleryImageView.java | 8 +- .../component/attach/MediaLayout.java | 218 +++++----- .../component/base/SettingView.java | 21 +- .../challegram/component/chat/InputView.java | 67 ++-- .../component/chat/MessageSenderButton.java | 14 +- .../component/chat/ReplyComponent.java | 3 + .../component/dialogs/ChatView.java | 6 +- .../component/sticker/StickerSetWrap.java | 8 + .../component/sticker/StickerSmallView.java | 6 +- .../org/thunderdog/challegram/data/TD.java | 42 -- .../thunderdog/challegram/data/TGChat.java | 2 +- .../thunderdog/challegram/data/TGMessage.java | 317 ++++++++++++--- .../challegram/data/TGReactions.java | 3 +- .../filegen/PhotoGenerationInfo.java | 37 ++ .../challegram/loader/ImageReceiver.java | 57 ++- .../mediaview/AvatarPickerMode.java | 26 ++ .../challegram/mediaview/EditButton.java | 85 +++- .../mediaview/MediaViewController.java | 258 ++++++++++-- .../mediaview/crop/CropAreaView.java | 39 +- .../challegram/mediaview/crop/CropState.java | 5 +- .../mediaview/crop/CropTargetView.java | 14 +- .../mediaview/gl/EGLEditorView.java | 9 + .../challegram/navigation/EditHeaderView.java | 38 +- .../challegram/navigation/ViewController.java | 21 + .../player/RecordAudioVideoController.java | 22 +- .../challegram/player/RoundVideoRecorder.java | 2 +- .../thunderdog/challegram/telegram/Tdlib.java | 56 ++- .../challegram/telegram/TdlibCache.java | 16 + .../challegram/telegram/TdlibUi.java | 376 +++++++++++++----- .../challegram/tool/DrawAlgorithms.java | 28 +- .../ui/CreateChannelController.java | 45 +-- .../challegram/ui/CreateGroupController.java | 31 +- .../challegram/ui/CreatePollController.java | 6 + .../challegram/ui/EditRightsController.java | 372 ++++++++++++----- .../ui/EmojiStatusListController.java | 11 +- .../ui/EmojiStatusSelectorEmojiPage.java | 2 +- .../challegram/ui/MessagesController.java | 187 +++++++-- .../challegram/ui/ProfileController.java | 80 +--- .../challegram/ui/SettingHolder.java | 1 + .../challegram/ui/SettingsAdapter.java | 1 + .../ui/SettingsBlockedController.java | 2 +- .../challegram/ui/SettingsController.java | 52 +-- .../ui/SettingsPrivacyKeyController.java | 55 ++- .../challegram/ui/ShareController.java | 56 ++- .../ui/camera/CameraController.java | 25 +- .../util/ProfilePhotoDrawModifier.java | 71 ++++ .../challegram/util/text/Counter.java | 15 +- .../challegram/widget/EmojiLayout.java | 4 +- .../challegram/widget/SendButton.java | 362 ++++++++++++++++- .../main/res/drawable/baseline_block_18.xml | 9 + .../baseline_format_list_bulleted_type_24.xml | 11 + .../main/res/drawable/baseline_info_14.xml | 9 + .../res/drawable/baseline_video_chat_24.xml | 11 + .../main/res/drawable/baseline_warning_14.xml | 9 + .../drawable/dot_baseline_acc_personal_18.xml | 17 + .../dot_baseline_account_circle_18.xml | 11 + .../dot_baseline_channel_accept_24.xml | 14 + .../dot_baseline_channel_circle_18.xml | 11 + .../dot_baseline_flip_horizontal_24.xml | 11 + .../drawable/dot_baseline_group_accept_24.xml | 14 + .../drawable/dot_baseline_group_circle_18.xml | 11 + .../drawable/dot_baseline_image_check_24.xml | 11 + .../dot_baseline_profile_accept_24.xml | 14 + app/src/main/res/values/ids.xml | 3 + app/src/main/res/values/strings.xml | 47 ++- 68 files changed, 2659 insertions(+), 794 deletions(-) create mode 100644 app/src/main/java/org/thunderdog/challegram/mediaview/AvatarPickerMode.java create mode 100644 app/src/main/java/org/thunderdog/challegram/util/ProfilePhotoDrawModifier.java create mode 100755 app/src/main/res/drawable/baseline_block_18.xml create mode 100644 app/src/main/res/drawable/baseline_format_list_bulleted_type_24.xml create mode 100755 app/src/main/res/drawable/baseline_info_14.xml create mode 100644 app/src/main/res/drawable/baseline_video_chat_24.xml create mode 100755 app/src/main/res/drawable/baseline_warning_14.xml create mode 100644 app/src/main/res/drawable/dot_baseline_acc_personal_18.xml create mode 100644 app/src/main/res/drawable/dot_baseline_account_circle_18.xml create mode 100644 app/src/main/res/drawable/dot_baseline_channel_accept_24.xml create mode 100644 app/src/main/res/drawable/dot_baseline_channel_circle_18.xml create mode 100644 app/src/main/res/drawable/dot_baseline_flip_horizontal_24.xml create mode 100644 app/src/main/res/drawable/dot_baseline_group_accept_24.xml create mode 100644 app/src/main/res/drawable/dot_baseline_group_circle_18.xml create mode 100644 app/src/main/res/drawable/dot_baseline_image_check_24.xml create mode 100644 app/src/main/res/drawable/dot_baseline_profile_accept_24.xml diff --git a/app/src/main/java/org/thunderdog/challegram/BaseActivity.java b/app/src/main/java/org/thunderdog/challegram/BaseActivity.java index 5bf2946f6f..21a4ff6102 100644 --- a/app/src/main/java/org/thunderdog/challegram/BaseActivity.java +++ b/app/src/main/java/org/thunderdog/challegram/BaseActivity.java @@ -1809,7 +1809,7 @@ public void hideContextualPopups (boolean byNavigation) { PopupLayout window = windows.get(i); View boundView = window.getBoundView(); ViewController boundController = window.getBoundController(); - if (isContextual(boundView) || (byNavigation && boundView instanceof StickerSetWrap) || (boundView instanceof MediaLayout && !(navigation.getCurrentStackItem() instanceof MessagesController)) || (byNavigation && isContextual(boundController)) ) { + if (isContextual(boundView) || (byNavigation && boundView instanceof StickerSetWrap) || (boundView instanceof MediaLayout && !((navigation.getCurrentStackItem() instanceof MessagesController) || (((MediaLayout) boundView).getMode() == MediaLayout.MODE_AVATAR_PICKER))) || (byNavigation && isContextual(boundController)) ) { window.hideWindow(true); } } @@ -3040,15 +3040,17 @@ public void checkCameraApi () { } private void initializeCamera (ViewController.CameraOpenOptions options) { - if (camera == null) { + final boolean needCreateCamera = camera == null; + if (needCreateCamera) { camera = new CameraController(this); - camera.setMode(options.mode, options.readyListener); - camera.setQrListener(options.qrCodeListener, options.qrModeSubtitle, options.qrModeDebug); + } + camera.setMode(options.mode, options.readyListener); + camera.setAvatarPickerMode(options.avatarPickerMode); + camera.setQrListener(options.qrCodeListener, options.qrModeSubtitle, options.qrModeDebug); + camera.setMediaEditorDelegates(options.delegate, options.selectDelegate, options.sendDelegate); + if (needCreateCamera) { camera.getValue(); // Ensure view creation addActivityListener(camera); - } else { - camera.setMode(options.mode, options.readyListener); - camera.setQrListener(options.qrCodeListener, options.qrModeSubtitle, options.qrModeDebug); } hideContextualPopups(false); closeAllMedia(true); diff --git a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaBottomGalleryController.java b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaBottomGalleryController.java index ddeb79c2af..6ceb8083a5 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaBottomGalleryController.java +++ b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaBottomGalleryController.java @@ -45,6 +45,7 @@ import org.thunderdog.challegram.data.TD; import org.thunderdog.challegram.loader.ImageFile; import org.thunderdog.challegram.loader.ImageGalleryFile; +import org.thunderdog.challegram.mediaview.AvatarPickerMode; import org.thunderdog.challegram.mediaview.MediaSelectDelegate; import org.thunderdog.challegram.mediaview.MediaSendDelegate; import org.thunderdog.challegram.mediaview.MediaViewController; @@ -56,6 +57,7 @@ import org.thunderdog.challegram.navigation.Menu; import org.thunderdog.challegram.navigation.MenuMoreWrap; import org.thunderdog.challegram.navigation.ToggleHeaderView; +import org.thunderdog.challegram.navigation.ViewController; import org.thunderdog.challegram.telegram.Tdlib; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.tool.Intents; @@ -101,7 +103,9 @@ protected int getMenuId () { @Override public void fillMenuItems (int id, HeaderView header, LinearLayout menu) { if (id == R.id.menu_more) { - header.addSearchButton(menu, this); + if (mediaLayout.getMode() != MediaLayout.MODE_AVATAR_PICKER) { + header.addSearchButton(menu, this); + } header.addMoreButton(menu, this); } else if (id == R.id.menu_clear) { header.addClearButton(menu, this); @@ -207,7 +211,8 @@ protected View onCreateView (Context context) { decoration = new GridSpacingItemDecoration(spanCount, Screen.dp(4f), true, true, true); GridLayoutManager manager = new RtlGridLayoutManager(context(), spanCount); - int options = MediaGalleryAdapter.OPTION_SELECTABLE | MediaGalleryAdapter.OPTION_ALWAYS_SELECTABLE; + int options = inAvatarPickerMode() ? MediaGalleryAdapter.OPTION_NEVER_SELECTABLE : + MediaGalleryAdapter.OPTION_SELECTABLE | MediaGalleryAdapter.OPTION_ALWAYS_SELECTABLE; /*if (U.deviceHasAnyCamera(context)) { options |= MediaGalleryAdapter.OPTION_CAMERA_AVAILABLE; }*/ @@ -229,7 +234,7 @@ protected View onCreateView (Context context) { if (mediaLayout.needCameraButton()) { cameraBadgeView = new CircleCounterBadgeView(this, R.id.btn_camera, this::onCameraButtonClick, null); cameraBadgeView.init(R.drawable.deproko_baseline_camera_26, 48f, 4f, ColorId.circleButtonChat, ColorId.circleButtonChatIcon); - cameraBadgeView.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(CircleCounterBadgeView.BUTTON_WRAPPER_WIDTH), Screen.dp(74f), Gravity.BOTTOM | Gravity.RIGHT, 0, 0, Screen.dp(12), Screen.dp(12 + 60))); + cameraBadgeView.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(CircleCounterBadgeView.BUTTON_WRAPPER_WIDTH), Screen.dp(74f), Gravity.BOTTOM | Gravity.RIGHT, 0, 0, Screen.dp(12), Screen.dp(12) + mediaLayout.getCameraButtonOffset())); contentView.addView(cameraBadgeView); } @@ -251,6 +256,12 @@ protected void onUpdateBottomBarFactor (float bottomBarFactor, float counterFact } private void onCameraButtonClick (View v) { + if (mediaLayout.getMode() == MediaLayout.MODE_AVATAR_PICKER) { + mediaLayout.hidePopupAndOpenCamera(new ViewController.CameraOpenOptions().anchor(v) + .setAvatarPickerMode(mediaLayout.getAvatarPickerMode()).setMediaEditorDelegates(this, this, this)); + return; + } + MessagesController c = mediaLayout.parentMessageController(); if (c == null) return; @@ -565,7 +576,7 @@ public boolean onPhotoOrVideoOpenRequested (ImageFile fromFile) { MediaViewController controller = new MediaViewController(context, tdlib); controller.setArguments( MediaViewController.Args.fromGallery(this, this, this, this, stack, mediaLayout.areScheduledOnly()) - .setReceiverChatId(mediaLayout.getTargetChatId()) + .setReceiverChatId(mediaLayout.getTargetChatId()).setAvatarPickerMode(mediaLayout.getAvatarPickerMode()) ); controller.open(); @@ -575,6 +586,10 @@ public boolean onPhotoOrVideoOpenRequested (ImageFile fromFile) { return false; } + private boolean inAvatarPickerMode () { + return mediaLayout.getAvatarPickerMode() != AvatarPickerMode.NONE; + } + @Override public boolean isMediaItemSelected (int index, MediaItem item) { return adapter.getSelectionIndex(item.getSourceGalleryFile()) >= 0; @@ -582,6 +597,9 @@ public boolean isMediaItemSelected (int index, MediaItem item) { @Override public void setMediaItemSelected (int index, MediaItem item, boolean isSelected) { + if (mediaLayout.getMode() == MediaLayout.MODE_AVATAR_PICKER) { + return; + } adapter.setSelected(item.getSourceGalleryFile(), isSelected); } diff --git a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryAdapter.java b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryAdapter.java index 9fdd05d898..005e51911a 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryAdapter.java +++ b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryAdapter.java @@ -37,11 +37,12 @@ public interface Callback { boolean onPhotoOrVideoOpenRequested (ImageFile fromFile); } - public static final int OPTION_SELECTABLE = 0x01; - public static final int OPTION_ALWAYS_SELECTABLE = 0x02; - public static final int OPTION_NEED_COUNTER = 0x04; - public static final int OPTION_CAMERA_AVAILABLE = 0x08; - public static final int OPTION_NEED_CAMERA = 0x10; + public static final int OPTION_SELECTABLE = 1; + public static final int OPTION_ALWAYS_SELECTABLE = 1 << 1; + public static final int OPTION_NEED_COUNTER = 1 << 2; + public static final int OPTION_CAMERA_AVAILABLE = 1 << 3; + public static final int OPTION_NEED_CAMERA = 1 << 4; + public static final int OPTION_NEVER_SELECTABLE = 1 << 5; private final Context context; private final RecyclerView parent; @@ -49,6 +50,7 @@ public interface Callback { private final Callback callback; private final boolean isSelectable; private final boolean isAlwaysSelectable; // when we click on a first photo, it will be selected + private final boolean isNeverSelectable; // hide all checkboxes private final boolean needCounter; private final boolean cameraAvailable; private boolean animationsEnabled; @@ -64,6 +66,7 @@ public MediaGalleryAdapter (Context context, RecyclerView parent, GridLayoutMana this.needCounter = (options & OPTION_NEED_COUNTER) != 0; this.cameraAvailable = false; // (options & OPTION_CAMERA_AVAILABLE) != 0 && (!Config.CUSTOM_CAMERA_ENABLED || (options & OPTION_NEED_CAMERA) != 0); this.showCamera = cameraAvailable && (options & OPTION_NEED_CAMERA) != 0; + this.isNeverSelectable = (options & OPTION_NEVER_SELECTABLE) != 0; this.selected = new ArrayList<>(); } @@ -244,6 +247,7 @@ public void onBindViewHolder (MediaHolder holder, int position) { ImageFile imageFile = images.get(showCamera ? position - 1 : position); holder.setImage(imageFile, getSelectionIndex(imageFile), isSelectable, isVisible(imageFile)); holder.setAnimationsDisabled(!animationsEnabled); + ((MediaGalleryImageView) holder.itemView).setAlwaysInvisible(isNeverSelectable); break; } case MediaHolder.VIEW_TYPE_COUNTER: { @@ -293,7 +297,7 @@ public int measureScrollTop (int position) { public void onClick (View view, boolean isSelect) { ImageFile image = ((MediaGalleryImageView) view).getImage(); - if (!isSelect && callback.onPhotoOrVideoOpenRequested(image)) { + if ((!isSelect || isNeverSelectable) && callback.onPhotoOrVideoOpenRequested(image)) { return; } diff --git a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryImageView.java b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryImageView.java index 981e997c7d..1912f929c7 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryImageView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaGalleryImageView.java @@ -81,6 +81,12 @@ public void setClickListener (ClickListener listener) { } private boolean isInvisible; + private boolean isAlwaysInvisible; + + public void setAlwaysInvisible (boolean alwaysInvisible) { + isAlwaysInvisible = alwaysInvisible; + invalidate(); + } public void setInvisible (boolean isInvisible, boolean needInvalidate) { if (this.isInvisible != isInvisible) { @@ -322,7 +328,7 @@ protected void onDraw (Canvas c) { c.restore(); } - if (!isInvisible) { + if (!isInvisible && !isAlwaysInvisible) { final int centerX = receiver.centerX() + (int) ((float) receiver.getWidth() * (1f - SCALE)) / 2; final int centerY = receiver.centerY() - (int) ((float) receiver.getHeight() * (1f - SCALE)) / 2; final int radius = Screen.dp(9f + 2f * factor); diff --git a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaLayout.java b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaLayout.java index a655b9ad98..c8b9d30dd6 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/attach/MediaLayout.java +++ b/app/src/main/java/org/thunderdog/challegram/component/attach/MediaLayout.java @@ -18,10 +18,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.TargetApi; -import android.content.Context; import android.content.Intent; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; @@ -29,7 +26,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -49,6 +45,7 @@ import org.thunderdog.challegram.data.TGUser; import org.thunderdog.challegram.loader.ImageFile; import org.thunderdog.challegram.loader.ImageGalleryFile; +import org.thunderdog.challegram.mediaview.AvatarPickerMode; import org.thunderdog.challegram.navigation.ActivityResultHandler; import org.thunderdog.challegram.navigation.BackHeaderButton; import org.thunderdog.challegram.navigation.BackListener; @@ -67,11 +64,8 @@ import org.thunderdog.challegram.theme.ThemeChangeListener; import org.thunderdog.challegram.theme.ThemeListenerList; import org.thunderdog.challegram.theme.ThemeManager; -import org.thunderdog.challegram.tool.Drawables; import org.thunderdog.challegram.tool.Fonts; import org.thunderdog.challegram.tool.Intents; -import org.thunderdog.challegram.tool.Paints; -import org.thunderdog.challegram.tool.PorterDuffPaint; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.UI; import org.thunderdog.challegram.tool.Views; @@ -81,9 +75,9 @@ import org.thunderdog.challegram.unsorted.Settings; import org.thunderdog.challegram.util.HapticMenuHelper; import org.thunderdog.challegram.util.Permissions; -import org.thunderdog.challegram.widget.AvatarView; import org.thunderdog.challegram.widget.NoScrollTextView; import org.thunderdog.challegram.widget.PopupLayout; +import org.thunderdog.challegram.widget.SendButton; import org.thunderdog.challegram.widget.ShadowView; import java.util.ArrayList; @@ -118,8 +112,10 @@ public interface MediaGalleryCallback extends MediaCallback { public static final int MODE_LOCATION = 1; public static final int MODE_GALLERY = 2; public static final int MODE_CUSTOM_POPUP = 3; + public static final int MODE_AVATAR_PICKER = 4; private int mode; + private @AvatarPickerMode int avatarPickerMode = AvatarPickerMode.NONE; private @Nullable MediaCallback callback; // Data @@ -132,7 +128,7 @@ public interface MediaGalleryCallback extends MediaCallback { private @Nullable ShadowView shadowView; private MediaBottomBaseController currentController; - private ViewGroup customBottomBar; + private View customBottomBar; private final ThemeListenerList themeListeners = new ThemeListenerList(); @@ -151,6 +147,18 @@ public void initDefault (MessagesController target) { init(MODE_DEFAULT, target); } + public int getMode () { + return mode; + } + + public int getAvatarPickerMode () { + return avatarPickerMode; + } + + public void setAvatarPickerMode (@AvatarPickerMode int avatarPickerMode) { + this.avatarPickerMode = avatarPickerMode; + } + private boolean rtl, needVote; public void init (int mode, MessagesController target) { @@ -169,6 +177,7 @@ public void init (int mode, MessagesController target) { index = 0; break; } + case MODE_AVATAR_PICKER: case MODE_GALLERY: { items = new MediaBottomBar.BarItem[] { new MediaBottomBar.BarItem(R.drawable.baseline_location_on_24, R.string.Gallery, ColorId.attachPhoto, Screen.dp(1f)) @@ -266,10 +275,7 @@ public void initCustom () { addView(controllerView); if (mode == MODE_DEFAULT) { - addView(customBottomBar = currentController.createCustomBottomBar()); - themeListeners.addThemeInvalidateListener(customBottomBar); - customBottomBar.setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM)); - customBottomBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY); + setCustomBottomBar(currentController.createCustomBottomBar()); } setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -277,6 +283,17 @@ public void initCustom () { Lang.addLanguageListener(this); } + public void setCustomBottomBar (View bottomBar) { + addView(customBottomBar = bottomBar); + themeListeners.addThemeInvalidateListener(customBottomBar); + customBottomBar.setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM)); + customBottomBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY); + } + + public View getCustomBottomBar () { + return customBottomBar; + } + @Override public void onLanguagePackEvent (int event, int arg1) { if (Lang.hasDirectionChanged(event, arg1)) { @@ -380,8 +397,11 @@ public MediaBottomBaseController createControllerForIndex (int index) { case MODE_LOCATION: { return new MediaBottomLocationController(this); } + case MODE_AVATAR_PICKER: case MODE_GALLERY: { - return new MediaBottomGalleryController(this); + MediaBottomGalleryController c = new MediaBottomGalleryController(this); + c.setArguments(new MediaBottomGalleryController.Arguments(mode == MODE_GALLERY)); + return c; } } if (rtl) { @@ -491,6 +511,10 @@ public void hide (boolean multi) { popupLayout.hideWindow(true); } + public boolean isVisible () { + return popupLayout != null && !popupLayout.isWindowHidden(); + } + // Callbacks @Override @@ -716,7 +740,7 @@ private void updateBarPosition () { int height = getBottomBarHeight(); float factor = Math.max(bottomBarFactor, counterFactor); float y = height - (int) ((float) height * factor); - if (!inSpecificMode()) { + if (!inSpecificMode() || mode == MODE_AVATAR_PICKER) { if (bottomBar != null) { if (currentController != null) { currentController.onUpdateBottomBarFactor(bottomBarFactor, counterFactor, y); @@ -724,6 +748,9 @@ private void updateBarPosition () { bottomBar.setTranslationY(y); onCurrentColorChanged(); } + if (currentController != null && mode == MODE_AVATAR_PICKER) { + currentController.onUpdateBottomBarFactor(bottomBarFactor, counterFactor, y); + } if (customBottomBar != null) { customBottomBar.setTranslationY(y); onCurrentColorChanged(); @@ -950,9 +977,8 @@ public void hidePopupAndOpenCamera (ViewController.CameraOpenOptions params) { @Override public void onPopupDismiss (PopupLayout popup) { if (cameraOpenOptions != null) { - MessagesController c = parentMessageController(); - if (c != null && !c.isDestroyed()) { - c.openInAppCamera(cameraOpenOptions); + if (parent != null && (parent instanceof MessagesController || mode == MODE_AVATAR_PICKER) && !parent.isDestroyed()) { + parent.openInAppCamera(cameraOpenOptions); } } performDestroy(); @@ -975,6 +1001,9 @@ public void performDestroy () { if (showKeyboardOnHide && target != null) { target.showKeyboard(); } + if (sendButton != null) { + sendButton.destroySlowModeCounterController(); + } ThemeManager.instance().removeThemeListener(this); Lang.removeLanguageListener(this); if (target != null) { @@ -1006,6 +1035,9 @@ public void pickDateOrProceed (TdlibUi.SimpleSendCallback sendCallback) { if (target != null && target.areScheduledOnly()) { tdlib().ui().showScheduleOptions(target, getTargetChatId(), false, sendCallback, null, null); } else { + if (showSlowModeRestriction(sendButton)) { + return; + } sendCallback.onSendRequested(Td.newSendOptions(), false); } } @@ -1116,6 +1148,10 @@ public void sendImage (ImageFile image, boolean isRemote) { } public boolean sendPhotosOrVideos (View view, ArrayList images, boolean areRemote, TdApi.MessageSendOptions options, boolean disableMarkdown, boolean asFiles, boolean disableAnimation) { + if (mode == MODE_AVATAR_PICKER) { + parent.context().forceCloseCamera(); + } + if (images == null || images.isEmpty()) { return false; } @@ -1275,12 +1311,11 @@ public void cancelMultiSelection () { // Counter private CounterHeaderView counterView; - private ImageView sendButton; + private SendButton sendButton; private HapticMenuHelper sendMenu; private BackHeaderButton closeButton; private TextView counterHintView; private ImageView groupMediaView, hotMediaView; - private @Nullable SenderSendIcon senderSendIcon; private float groupMediaFactor; private boolean needGroupMedia, needSpoiler; @@ -1347,37 +1382,28 @@ private void prepareCounter () { groupMediaFactor = needGroupMedia ? 1f : 0f; bottomBar.addView(counterHintView); - sendButton = new ImageView(getContext()) { + sendButton = new SendButton(getContext(), R.drawable.deproko_baseline_send_24) { @Override public boolean onTouchEvent (MotionEvent e) { return isEnabled() && Views.isValid(this) && super.onTouchEvent(e); } }; sendButton.setId(R.id.btn_send); - sendButton.setScaleType(ImageView.ScaleType.CENTER); - sendButton.setImageResource(R.drawable.deproko_baseline_send_24); - sendButton.setColorFilter(Theme.chatSendButtonColor()); + sendButton.getSlowModeCounterController(tdlib()).setCurrentChat(getTargetChatId()); themeListeners.addThemeFilterListener(sendButton, ColorId.chatSendButton); sendButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(55f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.RIGHT)); Views.setClickable(sendButton); sendButton.setOnClickListener(this); bottomBar.addView(sendButton); - TdApi.Chat chat = getTargetChat(); - if (chat != null && chat.messageSenderId != null) { - senderSendIcon = new SenderSendIcon(getContext(), tdlib(), chat.id); - senderSendIcon.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(19), Screen.dp(19), Gravity.RIGHT | Gravity.BOTTOM, 0, 0, Screen.dp(11), Screen.dp(8))); - senderSendIcon.update(chat.messageSenderId); - bottomBar.addView(senderSendIcon); - } - sendMenu = new HapticMenuHelper(list -> { + final TdApi.Chat chat = getTargetChat(); List items = tdlib().ui().fillDefaultHapticMenu(getTargetChatId(), false, getCurrentController().canRemoveMarkdown(), true); if (items == null) items = new ArrayList<>(); getCurrentController().addCustomItems(sendButton, items); - if (senderSendIcon != null) { - items.add(0, senderSendIcon.createHapticSenderItem(getTargetChat())); + if (chat != null && chat.messageSenderId != null) { + items.add(0, MediaLayout.createHapticSenderItem(tdlib(), chat)); } return !items.isEmpty() ? items : null; }, (menuItem, parentView, item) -> { @@ -1459,9 +1485,6 @@ public boolean onTouchEvent (MotionEvent e) { counterView.setAlpha(0f); sendButton.setAlpha(0f); - if (senderSendIcon != null) { - senderSendIcon.setAlpha(0f); - } closeButton.setAlpha(0f); counterHintView.setAlpha(0f); groupMediaView.setAlpha(0f); @@ -1642,9 +1665,6 @@ private void setCounterFactorInternal (float factor) { counterView.setAlpha(factor); sendButton.setAlpha(factor); closeButton.setAlpha(factor); - if (senderSendIcon != null) { - senderSendIcon.setAlpha(factor); - } checkCounterHint(); } setCounterEnabled(factor != 0f); @@ -1704,6 +1724,10 @@ private void animateCounterFactor (final float toFactor) { } public void setCounter (int count) { + if (mode == MODE_AVATAR_PICKER) { + return; + } + boolean init = counterFactor == 0f && count == 1; if (init) { prepareCounter(); @@ -1787,92 +1811,22 @@ public void setContentVisible (boolean visible) { } public boolean needCameraButton () { - return (parent instanceof MessagesController) && !((MessagesController) parent).isCameraButtonVisibleOnAttachPanel(); + return mode == MODE_AVATAR_PICKER || (parent instanceof MessagesController) && !((MessagesController) parent).isCameraButtonVisibleOnAttachPanel(); } - public static class SenderSendIcon extends FrameLayout { - private final AvatarView senderAvatarView; - private final Tdlib tdlib; - private final long chatId; - private boolean isPersonal; - private boolean isAnonymous; - private int backgroundColorId; - - public SenderSendIcon (@NonNull Context context, Tdlib tdlib, long chatId) { - super(context); - this.tdlib = tdlib; - this.chatId = chatId; - this.backgroundColorId = ColorId.filling; - - setWillNotDraw(false); - setLayoutParams(FrameLayoutFix.newParams(Screen.dp(19), Screen.dp(19))); - - senderAvatarView = new AvatarView(context); - senderAvatarView.setEnabled(false); - senderAvatarView.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(15), Screen.dp(15), Gravity.CENTER)); - addView(senderAvatarView); - } - - public void setBackgroundColorId (int backgroundColorId) { - this.backgroundColorId = backgroundColorId; - invalidate(); - } - - public AvatarView getSenderAvatarView () { - return senderAvatarView; - } - - public boolean isAnonymous () { - return isAnonymous; - } - - public boolean isPersonal () { - return isPersonal; - } - - @Override - protected void onDraw (Canvas c) { - float cx = getMeasuredWidth() / 2f; - float cy = getMeasuredHeight() / 2f; - c.drawCircle(cx, cy, Screen.dp(19f / 2f), Paints.fillingPaint(Theme.getColor(backgroundColorId))); - - if (isAnonymous) { - c.drawCircle(cx, cy, Screen.dp(15f / 2f), Paints.fillingPaint(Theme.iconLightColor())); - Drawable drawable = Drawables.get(getResources(), R.drawable.infanf_baseline_incognito_11); - Drawables.draw(c, drawable, cx - Screen.dp(5.5f), cy - Screen.dp(5.5f), PorterDuffPaint.get(ColorId.badgeMutedText)); - } - - super.onDraw(c); - } - - public void update (TdApi.MessageSender sender) { - final boolean isUserSender = Td.getSenderId(sender) == tdlib.myUserId(); - final boolean isGroupSender = Td.getSenderId(sender) == chatId; - - if (sender == null || isUserSender || isGroupSender) { - update(null, isUserSender, isGroupSender); - } else { - update(sender, false, false); - } - } + public int getCameraButtonOffset () { + return Screen.dp(60); + } - private void update (TdApi.MessageSender sender, boolean isPersonal, boolean isAnonymous) { - this.senderAvatarView.setVisibility(sender != null ? VISIBLE : GONE); - this.senderAvatarView.setMessageSender(tdlib, sender); - this.isAnonymous = isAnonymous; - this.isPersonal = isPersonal; - setVisibility(!isPersonal ? VISIBLE : GONE); - invalidate(); - } + public static HapticMenuHelper.MenuItem createHapticSenderItem (Tdlib tdlib, @NonNull TdApi.Chat chat) { + final long senderId = Td.getSenderId(chat.messageSenderId); - public HapticMenuHelper.MenuItem createHapticSenderItem (TdApi.Chat chat) { - if (isAnonymous()) { - return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), chat != null ? tdlib.getMessageSenderTitle(chat.messageSenderId) : null, R.drawable.dot_baseline_acc_anon_24); - } else if (isPersonal()) { - return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), chat != null ? tdlib.getMessageSenderTitle(chat.messageSenderId) : null, R.drawable.dot_baseline_acc_personal_24); - } else { - return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), chat != null ? tdlib.getMessageSenderTitle(chat.messageSenderId) : null, 0, tdlib, chat != null ? chat.messageSenderId : null, false); - } + if (senderId == chat.id) { + return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), tdlib.getMessageSenderTitle(chat.messageSenderId), R.drawable.dot_baseline_acc_anon_24); + } else if (senderId == tdlib.myUserId()) { + return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), tdlib.getMessageSenderTitle(chat.messageSenderId), R.drawable.dot_baseline_acc_personal_24); + } else { + return new HapticMenuHelper.MenuItem(R.id.btn_openSendersMenu, Lang.getString(R.string.SendAs), tdlib.getMessageSenderTitle(chat.messageSenderId), 0, tdlib, chat.messageSenderId, false); } } @@ -1893,11 +1847,19 @@ private void openSetSenderPopup () { } private void setNewMessageSender (TdApi.ChatMessageSender sender) { - tdlib().send(new TdApi.SetChatMessageSender(getTargetChatId(), sender.sender), tdlib().typedOkHandler(() -> { - TdApi.Chat chat = getTargetChat(); - if (senderSendIcon != null) { - senderSendIcon.update(chat != null ? chat.messageSenderId : null); - } - })); + tdlib().send(new TdApi.SetChatMessageSender(getTargetChatId(), sender.sender), tdlib().typedOkHandler()); + } + + public boolean showSlowModeRestriction (View v) { + CharSequence restriction = tdlib().getSlowModeRestrictionText(getTargetChatId()); + if (restriction != null) { + parent.context().tooltipManager() + .builder(v) + .controller(parent) + .show(parent.tdlib(), restriction).hideDelayed(); + return true; + } + + return false; } } diff --git a/app/src/main/java/org/thunderdog/challegram/component/base/SettingView.java b/app/src/main/java/org/thunderdog/challegram/component/base/SettingView.java index bc7f339e8c..db04307b3d 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/base/SettingView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/base/SettingView.java @@ -80,6 +80,7 @@ import me.vkryl.android.widget.FrameLayoutFix; import me.vkryl.core.BitwiseUtils; import me.vkryl.core.ColorUtils; +import me.vkryl.core.MathUtils; import me.vkryl.core.StringUtils; import me.vkryl.core.lambda.Destroyable; @@ -647,6 +648,12 @@ private void checkEmojiListener () { } } + private final BoolAnimator iconRotated = new BoolAnimator(this, AnimatorUtils.DECELERATE_INTERPOLATOR, 180L, false); + + public void setIconRotated (boolean rotated, boolean animated) { + iconRotated.setValue(rotated, animated); + } + private final BoolAnimator isEnabled = new BoolAnimator(this, AnimatorUtils.DECELERATE_INTERPOLATOR, 168l, true); public void setEnabledAnimated (boolean enabled) { @@ -825,7 +832,19 @@ protected void onDraw (Canvas c) { int width = getMeasuredWidth(); if (icon != null) { int x = (int) (rtl ? width - pIconLeft - icon.getMinimumWidth() : pIconLeft) + Screen.dp(24f) / 2 - icon.getMinimumWidth() / 2; - Drawables.draw(c, icon, x, pIconTop, lastIconResource == 0 ? Paints.getBitmapPaint() : iconColorId != 0 ? PorterDuffPaint.get(iconColorId) : Paints.getIconGrayPorterDuffPaint()); + float y = pIconTop; + final boolean needRotateIcon = iconRotated.getFloatValue() > 0; + if (needRotateIcon) { + float cx = x + icon.getMinimumWidth() / 2f; + float cy = y + icon.getMinimumHeight() / 2f; + c.save(); + c.rotate(MathUtils.fromTo(0, 90, iconRotated.getFloatValue()), cx, cy); + } + Drawables.draw(c, icon, x, y, lastIconResource == 0 ? Paints.getBitmapPaint() : iconColorId != 0 ? PorterDuffPaint.get(iconColorId) : Paints.getIconGrayPorterDuffPaint()); + if (needRotateIcon) { + c.restore(); + } + // c.drawBitmap(icon, x, pIconTop, paint); if (overlay != null) { c.save(); diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/InputView.java b/app/src/main/java/org/thunderdog/challegram/component/chat/InputView.java index 13dda70fd1..8395ae7da9 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/InputView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/InputView.java @@ -19,6 +19,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.text.Editable; @@ -48,6 +49,7 @@ import android.view.inputmethod.InputConnection; import android.widget.LinearLayout; +import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -91,9 +93,12 @@ import org.thunderdog.challegram.receiver.RefreshRateLimiter; import org.thunderdog.challegram.telegram.RightId; import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.Theme; +import org.thunderdog.challegram.tool.Drawables; import org.thunderdog.challegram.tool.Fonts; import org.thunderdog.challegram.tool.Paints; +import org.thunderdog.challegram.tool.PorterDuffPaint; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.Strings; import org.thunderdog.challegram.tool.UI; @@ -132,6 +137,7 @@ public class InputView extends NoClipEditText implements InlineSearchContext.Cal private Text placeholderTitle; private Text placeholderSubTitle; + private Drawable placeholderIcon; private CharSequence placeholderTitleText; private CharSequence placeholderSubtitleText; @@ -833,7 +839,7 @@ public void setInputPlaceholder (@StringRes int resId, Object... args) { return; } this.rawPlaceholder = placeholder; - setInputPlaceholder(placeholder, null); + setInputPlaceholder(placeholder, null, 0); /*if (controller == null) { // setHint(placeholder); } else { @@ -843,9 +849,10 @@ public void setInputPlaceholder (@StringRes int resId, Object... args) { }*/ } - public void setInputPlaceholder (CharSequence placeholder, CharSequence placeholderSubtitle) { + public void setInputPlaceholder (CharSequence placeholder, CharSequence placeholderSubtitle, @DrawableRes int iconId) { this.placeholderTitleText = placeholder; this.placeholderSubtitleText = placeholderSubtitle; + this.placeholderIcon = iconId != 0 ? Drawables.get(getResources(), iconId) : null; if (controller != null) { this.lastPlaceholderAvailWidth = 0; checkPlaceholderWidth(); @@ -858,8 +865,8 @@ private boolean needAnimateChanges () { } public void checkPlaceholderWidth () { - if ((lastPlaceholderRes != 0 || !StringUtils.isEmpty(placeholderTitleText)) && controller != null) { - int availWidth = Math.max(0, getMeasuredWidth() - controller.getHorizontalInputPadding() - getPaddingLeft()); + if ((lastPlaceholderRes != 0 || !StringUtils.isEmpty(placeholderTitleText) || placeholderIcon != null) && controller != null) { + int availWidth = Math.max(0, getMeasuredWidth() - controller.getHorizontalInputPadding() - getPaddingLeft() - Screen.dp(placeholderIcon != null ? 20 : 0)); if (this.lastPlaceholderAvailWidth != availWidth) { this.lastPlaceholderAvailWidth = availWidth; @@ -1046,7 +1053,7 @@ public int provideCurrentStringResource () { public void onEmojiSelected (String emoji) { TextSelection selection = getTextSelection(); - if (selection == null) + if (selection == null || !isEnabled()) return; int after = selection.start + emoji.length(); SpannableString s = new SpannableString(emoji); @@ -1073,7 +1080,7 @@ public void onCustomEmojiSelected (TGStickerObj stickerObj, boolean needReplace) public void onCustomEmojiSelected (TdApi.Sticker stickerObj, boolean needReplace) { TextSelection selection = getTextSelection(); - if (selection == null) + if (selection == null || !isEnabled()) return; final String emoji = TD.stickerEmoji(stickerObj); @@ -1142,10 +1149,15 @@ public void updateMessageHint (TdApi.Chat chat, @Nullable ThreadInfo messageThre return; } int resource; + int icon = 0; CharSequence subplaceholder = null; Object[] args = null; TdApi.ChatMemberStatus status = tdlib.chatStatus(chat.id); - if (tdlib.isChannel(chat.id)) { + + if (!tdlib.canSendBasicMessage(chat)) { + resource = R.string.MessageInputTextDisabled; + icon = R.drawable.baseline_block_18; + } else if (tdlib.isChannel(chat.id)) { resource = isSilent ? R.string.ChannelSilentBroadcast : R.string.ChannelBroadcast; } /*else if (tdlib.isMultiChat(chat) && Td.isAnonymous(status)) { resource = messageThread != null ? (messageThread.areComments() ? R.string.CommentAnonymously : R.string.MessageReplyAnonymously) : R.string.MessageAnonymously; @@ -1166,7 +1178,7 @@ public void updateMessageHint (TdApi.Chat chat, @Nullable ThreadInfo messageThre } else { text = customInputField; } - setInputPlaceholder(text, subplaceholder); + setInputPlaceholder(text, subplaceholder, icon); } public void setDraft (@Nullable TdApi.InputMessageContent draftContent) { @@ -1254,6 +1266,7 @@ private void drawEmojiOverlay (Canvas c) { @Override protected void onDraw (Canvas c) { + final int x = getPaddingLeft() + Screen.dp(placeholderIcon != null ? 20 : 0); final float alpha = showPlaceholder.getFloatValue(); final int offset = (int) (hasSubPlaceholder.getFloatValue() * (getTextSize() / 18 * 8)); final int baseline = getBaseline(); @@ -1263,15 +1276,16 @@ protected void onDraw (Canvas c) { final int titleHeight = placeholderTitle.getHeight(); final int titleBaseline = (int)(titleHeight * 0.75f); final int y = baseline - titleBaseline - offset; - placeholderTitle.draw(c, getPaddingLeft(), y, null, alpha); - //c.drawRect(getPaddingLeft(), y, getPaddingLeft() + placeholderTitle.getWidth(), y + placeholderTitle.getHeight(), Paints.strokeSmallPaint(Color.GREEN)); - //c.drawRect(getPaddingLeft(), y, getPaddingLeft() + placeholderTitle.getWidth(), y + placeholderTitle.getLineHeight(), Paints.strokeSmallPaint(Color.GREEN)); + placeholderTitle.draw(c, x, y, null, alpha); } for (ListAnimator.Entry entry : subtitleReplaceAnimator) { final int offset2 = (int) ((!entry.isAffectingList() ? ((entry.getVisibility() - 1f) * (getTextSize() / 18f * 14f)): ((1f - entry.getVisibility()) * (getTextSize() / 18f * 14f)))); - entry.item.draw(c, getPaddingLeft(), baseline - offset / 2 + offset2, null, Math.min(alpha, entry.getVisibility())); + entry.item.draw(c, x, baseline - offset / 2 + offset2, null, Math.min(alpha, entry.getVisibility())); + } + if (placeholderIcon != null) { + Drawables.draw(c, placeholderIcon, getPaddingLeft(), (getMeasuredHeight() - placeholderIcon.getMinimumHeight()) / 2f, PorterDuffPaint.get(ColorId.iconLight) /*Paints.getPorterDuffPaint(ColorId.textPlaceholder)*/); } } @@ -1281,11 +1295,9 @@ protected void onDraw (Canvas c) { String text = getText().toString(); if (text.equalsIgnoreCase(prefix)) { checkPrefix(text); - c.drawText(displaySuffix, getPaddingLeft() + prefixWidth, getBaseline(), paint); + c.drawText(displaySuffix, x + prefixWidth, getBaseline(), paint); } } - // c.drawRect(0, baseline, getMeasuredWidth(), baseline, Paints.strokeSmallPaint(Color.RED)); - // c.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Paints.strokeBigPaint(Color.RED)); } // Inline query @@ -1398,8 +1410,14 @@ protected InputConnection createInputConnection (EditorInfo editorInfo) { return null; final InputConnectionCompat.OnCommitContentListener callback = (inputContentInfo, flags, bundle) -> { - if (controller == null || !controller.hasWritePermission()) + if (controller == null) + return false; + + final long chatId = controller.getChatId(); + final TdApi.Chat chat = tdlib.chat(chatId); + if (chat == null) { return false; + } ClipDescription description = inputContentInfo.getDescription(); @MediaType int mediaType; @@ -1427,7 +1445,6 @@ protected InputConnection createInputConnection (EditorInfo editorInfo) { } Uri uri = inputContentInfo.getContentUri(); long timestamp = System.currentTimeMillis(); - long chatId = controller.getChatId(); long messageThreadId = controller.getMessageThreadId(); TdApi.InputMessageReplyTo replyTo = controller.obtainReplyTo(); boolean silent = controller.obtainSilentMode(); @@ -1471,8 +1488,12 @@ protected InputConnection createInputConnection (EditorInfo editorInfo) { TdApi.InputFileGenerated generated = PhotoGenerationInfo.newFile(path, 0, timestamp, false, 0); content = tdlib.filegen().createThumbnail(new TdApi.InputMessagePhoto(generated, null, null, imageWidth, imageHeight, null, null, false), isSecretChat); } - if (needMenu) { - tdlib.ui().post(() -> { + + UI.post(() -> { + if (controller.showRestriction(this, tdlib.getRestrictionText(chat, content))) { + return; + } + if (needMenu) { tdlib.ui().showScheduleOptions(controller, chatId, false, (sendOptions, disableMarkdown) -> tdlib.sendMessage(chatId, messageThreadId, replyTo, @@ -1481,10 +1502,10 @@ protected InputConnection createInputConnection (EditorInfo editorInfo) { null ), null, null); - }); - } else { - tdlib.sendMessage(chatId, messageThreadId, replyTo, Td.newSendOptions(silent), content); - } + } else { + tdlib.sendMessage(chatId, messageThreadId, replyTo, Td.newSendOptions(silent), content); + } + }); }); // read and display inputContentInfo asynchronously. // call inputContentInfo.releasePermission() as needed. diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageSenderButton.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageSenderButton.java index 6d5d8cbe5a..db5e87b8d3 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageSenderButton.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageSenderButton.java @@ -183,6 +183,7 @@ public void checkPosition () { public void setSendFactor (float factor) { sendFactor = factor; checkPositionAndSize(); + checkAlpha(); } public void checkPositionAndSize () { @@ -246,13 +247,24 @@ public void onItemChanged (ReplaceAnimator animator) { } } + private boolean inSlowMode; + + public void setInSlowMode (boolean inSlowMode) { + this.inSlowMode = inSlowMode; + checkAlpha(); + } + + private void checkAlpha () { + setAlpha(alphaAnimator.getFloatValue() * (inSlowMode ? (1f - sendFactor) : 1f)); + } + @Override public void onFactorChanged (int id, float factor, float fraction, FactorAnimator callee) { if (id == QUICK_ANIMATOR) { currentButtonView.setQuickSelectFactor(factor); oldButtonView.setQuickSelectFactor(factor); } else if (id == VISIBLE_ANIMATOR) { - setAlpha(factor); + checkAlpha(); } invalidate(); } diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/ReplyComponent.java b/app/src/main/java/org/thunderdog/challegram/component/chat/ReplyComponent.java index 483ee13e06..870664bf89 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/ReplyComponent.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/ReplyComponent.java @@ -884,6 +884,9 @@ private void setMessage (TdApi.Message msg, boolean forceRequestImage, boolean f } } String title = computeTitleText(null); + if (parent != null) { + UI.execute(parent::onReplyLoaded); + } if (Thread.currentThread() == Background.instance().thread() || forceLocal) { this.content = new ContentPreview(translatedText, contentPreview); setTitleImpl(title); diff --git a/app/src/main/java/org/thunderdog/challegram/component/dialogs/ChatView.java b/app/src/main/java/org/thunderdog/challegram/component/dialogs/ChatView.java index 7bfbdf6b0f..738d18d16c 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/dialogs/ChatView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/dialogs/ChatView.java @@ -623,8 +623,8 @@ protected void onDraw (Canvas c) { Drawables.drawRtl(c, Icons.getClockIcon(ColorId.iconLight), x, getClockTop(chatListMode) - Screen.dp(Icons.CLOCK_SHIFT_Y), Paints.getIconLightPorterDuffPaint(), viewWidth, rtl); } else { int x = chat.getChecksRight(); + int y = getClockTop(chatListMode); if (chat.isOutgoing() && !chat.isSelfChat()) { - int y = getClockTop(chatListMode); if (chat.showViews()) { y -= Screen.dp(.5f); } else if (chat.isUnread()) { @@ -637,11 +637,11 @@ protected void onDraw (Canvas c) { int iconX = x - Screen.dp(Icons.TICKS_SHIFT_X) - Screen.dp(14f); boolean unread = chat.isUnread(); Drawables.drawRtl(c, unread ? Icons.getSingleTick(ColorId.ticks) : Icons.getDoubleTick(ColorId.ticks), iconX, y - Screen.dp(Icons.TICKS_SHIFT_Y), unread ? Paints.getTicksPaint() : Paints.getTicksReadPaint(), viewWidth, rtl); - x -= Screen.dp(24 + 3); + x -= Screen.dp(24 - 8 + 3); } } if (chat.needDrawReactionsPreview()) { - chat.getReactionsCounterDrawable().draw(c, x - chat.getReactionsWidth(), getClockTop(chatListMode) + Screen.dp(7f)); + chat.getReactionsCounterDrawable().draw(c, x - chat.getReactionsWidth(), y + Screen.dp(6f)); } } diff --git a/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSetWrap.java b/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSetWrap.java index 93e16b860b..b54bead7a1 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSetWrap.java +++ b/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSetWrap.java @@ -596,14 +596,21 @@ private int getHeaderTop () { return provideOffset() - stickersController.getOffsetScroll(); } + private boolean isScrollByHeader; + @Override public void onScrollFinished () { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (isScrollByHeader) { + isScrollByHeader = false; + return; + } if (Config.USE_FULLSCREEN_NAVIGATION) { if (topLick != null) { if (topLick.factor >= .4f) { stickersController.scrollBy((int) ((float) HeaderView.getTopOffset() * (1f - topLick.factor))); + isScrollByHeader = true; } else { stickersController.scrollBy(-(int) ((float) HeaderView.getTopOffset() * topLick.factor)); } @@ -613,6 +620,7 @@ public void onScrollFinished () { if (statusBarFactor != 0f && statusBarFactor != 1f) { if (statusBarFactor >= .4f) { stickersController.scrollBy(getHeaderTop()); + isScrollByHeader = true; } else { stickersController.scrollBy(-(getStatusBarLimit() - getHeaderTop())); } diff --git a/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSmallView.java b/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSmallView.java index 69d8b0b7c6..9eab67527b 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSmallView.java +++ b/app/src/main/java/org/thunderdog/challegram/component/sticker/StickerSmallView.java @@ -325,7 +325,7 @@ default void onStickerPreviewChanged (StickerSmallView view, TGStickerObj otherO default int getStickerViewTop (StickerSmallView v) { return -1; } default StickerSmallView getStickerViewUnder (StickerSmallView v, int x, int y) { return null; } default TGReaction getReactionForPreview (StickerSmallView v) { return null; } - default void onSetEmojiStatusFromPreview (StickerSmallView view, View clickView, TGStickerObj sticker, long emojiId, int duration) { } + default void onSetEmojiStatusFromPreview (StickerSmallView view, View clickView, TGStickerObj sticker, long emojiId, long expirationDate) { } } private @Nullable StickerMovementCallback callback; @@ -578,9 +578,9 @@ public void closePreviewIfNeeded () { } } - public void onSetEmojiStatus (View view, TGStickerObj sticker, long emojiId, int duration) { + public void onSetEmojiStatus (View view, TGStickerObj sticker, long emojiId, long expirationDate) { if (callback != null) { - callback.onSetEmojiStatusFromPreview(this, view, sticker, emojiId, duration); + callback.onSetEmojiStatusFromPreview(this, view, sticker, emojiId, expirationDate); } } diff --git a/app/src/main/java/org/thunderdog/challegram/data/TD.java b/app/src/main/java/org/thunderdog/challegram/data/TD.java index cc4fe72d55..763b68e506 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TD.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TD.java @@ -2800,48 +2800,6 @@ public static boolean matchHashtag (char c) { return type != Character.SPACE_SEPARATOR && c != '#'; } - /*public static boolean hasWritePermission (TdApi.Chat chat) { - if (chat == null) { - return false; - } - switch (chat.type.getConstructor()) { - case TdApi.ChatTypeSupergroup.CONSTRUCTOR: { - TdApi.Supergroup channel = TdlibCache.instance().getSupergroup(TD.getChatSupergroupId(chat)); - return hasWritePermission(channel); - } - case TdApi.ChatTypeBasicGroup.CONSTRUCTOR: { - TdApi.BasicGroup group = TdlibCache.instance().getGroup(TD.getChatBasicGroupId(chat)); - return hasWritePermission(group); - } - case TdApi.ChatTypePrivate.CONSTRUCTOR: { - TdApi.User user = TD.getUser(chat); - return user != null && user.type.getConstructor() != TdApi.UserTypeDeleted.CONSTRUCTOR && user.type.getConstructor() != TdApi.UserTypeUnknown.CONSTRUCTOR; - } - case TdApi.ChatTypeSecret.CONSTRUCTOR: { - TdApi.SecretChat secretChat = TD.getSecretChat(chat); - return secretChat != null && secretChat.state.getConstructor() == TdApi.SecretChatStateReady.CONSTRUCTOR; - } - } - return false; - }*/ - - public static boolean hasWritePermission (TdApi.Supergroup supergroup) { - if (supergroup != null) { - if (supergroup.isChannel) { - switch (supergroup.status.getConstructor()) { - case TdApi.ChatMemberStatusCreator.CONSTRUCTOR: - return true; - case TdApi.ChatMemberStatusAdministrator.CONSTRUCTOR: - return ((TdApi.ChatMemberStatusAdministrator) supergroup.status).rights.canPostMessages; - } - return false; - } else { - return !isNotInChat(supergroup.status); - } - } - return false; - } - public static boolean isLocalLanguagePackId (String languagePackId) { return languagePackId.startsWith("X"); } diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGChat.java b/app/src/main/java/org/thunderdog/challegram/data/TGChat.java index cbf8c21213..0ab6507420 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGChat.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGChat.java @@ -969,7 +969,7 @@ public long mediaTextComplexColor () { } public boolean needDrawReactionsPreview () { - return isPrivate() && !isSelfChat(); + return isPrivate() && !isSelfChat() && !showDraft(); } public @Nullable EmojiStatusHelper.EmojiStatusDrawable getEmojiStatus () { 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 acac48f224..a1c0b27243 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java @@ -234,16 +234,22 @@ public abstract class TGMessage implements InvalidateContentProvider, TdlibDeleg // counters - private final Counter viewCounter, replyCounter, shareCounter, isPinned; + private final Counter viewCounter, replyCounter, shareCounter, isPinned, isEdited, isRestricted, isUnsupported; private Counter shrinkedReactionsCounter, reactionsCounter; private final ReactionsCounterDrawable reactionsCounterDrawable; private final Counter isChannelHeaderCounter; - private float isChannelHeaderCounterX, isChannelHeaderCounterY; private boolean translatedCounterForceShow; private final Counter isTranslatedCounter; private final TranslationCounterDrawable isTranslatedCounterDrawable; - private float isTranslatedCounterX, isTranslatedCounterY; + + // counter last-draw positions + + private final RectF isChannelHeaderCounterLastDrawRect = new RectF(); + private final RectF isTranslatedCounterLastDrawRect = new RectF(); + private final RectF isRestrictedCounterLastDrawRect = new RectF(); + private final RectF isEditedCounterLastDrawRect = new RectF(); + // forward values @@ -422,6 +428,28 @@ public void onInvalidateReceiversRequested () { .callback(this) .drawable(R.drawable.deproko_baseline_pin_14, 14f, 0f, Gravity.CENTER_HORIZONTAL) .build(); + this.isEdited = new Counter.Builder() + .noBackground() + .allBold(false) + .callback(this) + .drawable(R.drawable.baseline_edit_12, 12f, 0f, Gravity.CENTER_HORIZONTAL) + .build(); + this.isEdited.showHide(true, false); + this.isRestricted = new Counter.Builder() + .noBackground() + .allBold(false) + .callback(this) + .drawable(R.drawable.baseline_warning_14, 14f, 0f, Gravity.CENTER_HORIZONTAL) + .colorSet(() -> Theme.getColor(ColorId.messageNegativeLine)) + .build(); + this.isRestricted.showHide(true, false); + this.isUnsupported = new Counter.Builder() + .noBackground() + .allBold(false) + .callback(this) + .drawable(R.drawable.baseline_info_14, 14f, 0f, Gravity.CENTER_HORIZONTAL) + .build(); + this.isUnsupported.showHide(true, false); this.isChannelHeaderCounter = new Counter.Builder() .noBackground() .allBold(false) @@ -531,6 +559,13 @@ public void onInvalidateReceiversRequested () { checkHighlightedText(); UI.post(() -> updateReactionAvatars(false)); + + this.isHiddenByFilter = new BoolAnimator(IS_HIDDEN_BY_MESSAGE_FILTER_ANIMATOR_ID, (a, b, c, d) -> { + if (BitwiseUtils.hasFlag(flags, FLAG_LAYOUT_BUILT)) { + notifyBubbleChanged(); + invalidate(); + } + }, AnimatorUtils.DECELERATE_INTERPOLATOR, 320L); } private static @NonNull T nonNull (@Nullable T value) { @@ -894,6 +929,26 @@ protected final boolean useForward () { return msg.forwardInfo != null && (!useBubbles() || !separateReplyFromBubble()) && !forceForwardOrImportInfo(); } + + + // + + private static final int IS_HIDDEN_BY_MESSAGE_FILTER_ANIMATOR_ID = 2; + + private static final int HIDDEN_BY_MESSAGE_FILTER_HEIGHT = 35; + + private final BoolAnimator isHiddenByFilter; + + public void setIsHiddenByMessagesFilter (boolean hidden, boolean animated) { + isHiddenByFilter.setValue(hidden && !isSponsoredMessage(), BitwiseUtils.hasFlag(flags, FLAG_LAYOUT_BUILT) && currentViews.hasAnyTargetToInvalidate() && UI.inUiThread() && controller() != null && controller().isFocused() && animated); + } + + public boolean isHiddenByMessagesFilter () { + return isHiddenByFilter.getValue(); + } + + + private static final int VIEW_COUNT_HIDDEN = 0; private static final int VIEW_COUNT_MAIN = 1; private static final int VIEW_COUNT_FORWARD = 2; @@ -1317,8 +1372,10 @@ protected final int getExtraPadding () { } public int computeHeight () { + final int headerPadding = getHeaderPadding(); + final int extraPadding = getExtraPadding(); if (useBubbles()) { - int height = bottomContentEdge + getPaddingBottom() + getExtraPadding(); + int height = bottomContentEdge + getPaddingBottom() + extraPadding; if (inlineKeyboard != null && !inlineKeyboard.isEmpty()) { height += inlineKeyboard.getHeight() + TGInlineKeyboard.getButtonSpacing(); } @@ -1332,9 +1389,9 @@ public int computeHeight () { if (commentButton.isBubble()) { height += commentButton.getAnimatedHeight(Screen.dp(5f), commentButton.getVisibility()); } - return height; + return MathUtils.fromTo(height, Screen.dp(HIDDEN_BY_MESSAGE_FILTER_HEIGHT) + extraPadding + headerPadding, isHiddenByFilter.getFloatValue()); } else { - int height = pContentY + getContentHeight() + getPaddingBottom() + getExtraPadding(); + int height = pContentY + getContentHeight() + getPaddingBottom() + extraPadding; if (inlineKeyboard != null && !inlineKeyboard.isEmpty()) { height += inlineKeyboard.getHeight() + xPaddingBottom; } @@ -1348,7 +1405,7 @@ public int computeHeight () { if (commentButton.isVisible() && commentButton.isInline()) { height += commentButton.getAnimatedHeight(useReactionBubbles ? -Screen.dp(2f) : 0, commentButton.getVisibility()); } - return height; + return MathUtils.fromTo(height, Screen.dp(HIDDEN_BY_MESSAGE_FILTER_HEIGHT) + extraPadding + headerPadding, isHiddenByFilter.getFloatValue()); } } @@ -1390,6 +1447,10 @@ private boolean shouldShowEdited () { return !headerDisabled() && (isEdited() || isBeingEdited()) && msg.viaBotUserId == 0 && !sender.isBot() && !sender.isServiceAccount() && (useBubbles() ? useBubbleTime() : (!isOutgoing() || hasHeader() || !shouldShowTicks())) && (getViewCount() > 0 || !isEventLog()); } + private boolean shouldShowMessageRestrictedWarning () { + return BitwiseUtils.hasFlag(flags, FLAG_UNSUPPORTED) || isRestrictedByTelegram(); + } + private boolean needAvatar () { if (!useBubbles()) { return true; @@ -1880,6 +1941,12 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat checkEdges(); + final float isHiddenFactor = isHiddenByFilter.getFloatValue(); + if (isHiddenFactor == 1f) { + drawHiddenMessage(view, c, isHiddenFactor); + return; + } + // "Unread messages" / "Discussion started" badge if ((flags & FLAG_SHOW_BADGE) != 0) { int top = 0; @@ -2033,7 +2100,7 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat hAdminNameT.draw(c, right, top - Screen.dp(12f)); } if (useBubbles && needDrawChannelIconInHeader() && hAuthorNameT != null) { - isChannelHeaderCounter.draw(c, isChannelHeaderCounterX = (right - Screen.dp(6)), isChannelHeaderCounterY = (top - Screen.dp(5)), Gravity.RIGHT | Gravity.BOTTOM, 1f, view, isOutgoing() ? ColorId.bubbleOut_time : ColorId.bubbleIn_time); + isChannelHeaderCounter.draw(c, (right - Screen.dp(6)), (top - Screen.dp(5)), Gravity.RIGHT | Gravity.BOTTOM, 1f, view, isOutgoing() ? ColorId.bubbleOut_time : ColorId.bubbleIn_time, isChannelHeaderCounterLastDrawRect); } } } @@ -2053,7 +2120,7 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat int viewsX = pTicksLeft - Icons.getSingleTickWidth() + ((flags & FLAG_HEADER_ENABLED) != 0 ? 0 : Screen.dp(1f)) - Screen.dp(Icons.TICKS_SHIFT_X); if (needDrawChannelIconInHeader() && hAuthorNameT != null) { - isChannelHeaderCounter.draw(c, isChannelHeaderCounterX = ((isSending() ? clockX : viewsX) + Screen.dp(7)), isChannelHeaderCounterY = (pTicksTop + Screen.dp(5)), Gravity.LEFT, 1f, view, ColorId.iconLight); + isChannelHeaderCounter.draw(c, ((isSending() ? clockX : viewsX) + Screen.dp(7)), (pTicksTop + Screen.dp(5)), Gravity.LEFT, 1f, view, ColorId.iconLight, isChannelHeaderCounterLastDrawRect); clockX -= isChannelHeaderCounter.getScaledWidth(Screen.dp(1)); viewsX -= isChannelHeaderCounter.getScaledWidth(Screen.dp(1)); } @@ -2075,14 +2142,24 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat // Edited if (shouldShowEdited()) { - // right -= Icons.getEditedIconWidth(); - right -= Icons.getEditedIconWidth(); if (isBeingEdited()) { + right -= Icons.getEditedIconWidth(); + right -= Screen.dp(COUNTER_ADD_MARGIN); Drawables.draw(c, Icons.getClockIcon(ColorId.iconLight), pTicksLeft - (shouldShowTicks() ? Icons.getSingleTickWidth() + Screen.dp(2.5f) : 0) - Icons.getEditedIconWidth() - Screen.dp(6f), pTicksTop - Screen.dp(5f), Paints.getIconLightPorterDuffPaint()); } else { - Drawables.draw(c, view.getSparseDrawable(R.drawable.baseline_edit_12, ColorId.NONE), right, pTicksTop, Paints.getIconLightPorterDuffPaint()); + isEdited.draw(c, right, top, Gravity.RIGHT, 1f, view, ColorId.iconLight, isEditedCounterLastDrawRect); + right -= isEdited.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); + } + } + + if (shouldShowMessageRestrictedWarning()) { + if (isRestrictedByTelegram()) { + isRestricted.draw(c, right, top, Gravity.RIGHT, 1f, view, 0, isRestrictedCounterLastDrawRect); + right -= isRestricted.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); + } else { + isUnsupported.draw(c, right, top, Gravity.RIGHT, 1f, view, ColorId.iconLight, isRestrictedCounterLastDrawRect); + right -= isUnsupported.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); } - right -= Screen.dp(COUNTER_ADD_MARGIN); } isPinned.draw(c, right, top, Gravity.RIGHT, 1f, view, getTimePartIconColorId()); @@ -2102,8 +2179,7 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat } if (translationStyleMode() == Settings.TRANSLATE_MODE_INLINE) { - isTranslatedCounter.draw(c, right, isTranslatedCounterY = top, Gravity.RIGHT, 1f); - isTranslatedCounterX = right - Screen.dp(10); + isTranslatedCounter.draw(c, right, top, Gravity.RIGHT, 1f, isTranslatedCounterLastDrawRect); right -= isTranslatedCounter.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN)); } if (reactionsDrawMode == REACTIONS_DRAW_MODE_FLAT) { @@ -2367,6 +2443,19 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat startSetReactionAnimationIfReady(); highlightUnreadReactionsIfNeeded(); + if (isHiddenFactor > 0f) { + drawHiddenMessage(view, c, isHiddenFactor); + } + } + + public final void drawHiddenMessage (MessageView view, Canvas c, float isHiddenFactor) { + final int viewWidth = view.getMeasuredWidth(); + final int viewHeight = view.getMeasuredHeight(); + final int y = getHeaderPadding(); + + c.drawRect(0, y, viewWidth, viewHeight, Paints.fillingPaint(ColorUtils.alphaColor(isHiddenFactor, Theme.getColor(ColorId.filling)))); + // c.drawRect(0, y, viewWidth, y + 1, Paints.fillingPaint(ColorUtils.alphaColor(isHiddenFactor, Theme.getColor(ColorId.separator)))); + c.drawRect(0, viewHeight - 1, viewWidth, viewHeight, Paints.fillingPaint(ColorUtils.alphaColor(isHiddenFactor, Theme.getColor(ColorId.separator)))); } protected final boolean needColoredNames () { @@ -2750,19 +2839,38 @@ public boolean shouldIgnoreTap (MotionEvent e) { return e.getY() < findTopEdge(); } + private static boolean checkClickOnRect (RectF rectF, float x, float y, float accuracy) { + RectF rect = Paints.getRectF(); + rect.set(rectF); + rect.inset(-accuracy, -accuracy); + return rect.contains(x, y); + } + private int getClickType (MessageView view, float x, float y) { if (isTranslated()) { - if (MathUtils.distance(isTranslatedCounterX, isTranslatedCounterY, x, y) < Screen.dp(8)) { + if (checkClickOnRect(isTranslatedCounterLastDrawRect, x, y, Screen.dp(4))) { return CLICK_TYPE_TRANSLATE_MESSAGE_ICON; } } if (needDrawChannelIconInHeader() && hAuthorNameT != null) { - if (MathUtils.distance(isChannelHeaderCounterX, isChannelHeaderCounterY, x, y) < Screen.dp(8)) { + if (checkClickOnRect(isChannelHeaderCounterLastDrawRect, x, y, Screen.dp(4))) { return CLICK_TYPE_CHANNEL_MESSAGE_ICON; } } + if (shouldShowMessageRestrictedWarning()) { + if (checkClickOnRect(isRestrictedCounterLastDrawRect, x, y, Screen.dp(4))) { + return CLICK_TYPE_MESSAGE_RESTRICTED_ICON; + } + } + + if (shouldShowEdited()) { + if (checkClickOnRect(isEditedCounterLastDrawRect, x, y, Screen.dp(4))) { + return CLICK_TYPE_MESSAGE_EDITED_ICON; + } + } + if (replyData != null && replyData.isInside(x, y, useBubbles() && !useBubble())) { return CLICK_TYPE_REPLY; } @@ -2790,6 +2898,28 @@ public void onClickAt (View view, float x, float y) { openMessageFromChannel(); break; } + case CLICK_TYPE_MESSAGE_RESTRICTED_ICON: { + showMessageTooltip((targetView, outRect) -> { + isRestrictedCounterLastDrawRect.round(outRect); + outRect.top -= Screen.dp(6); + }, Lang.getString(isRestrictedByTelegram() ? + R.string.MessageRestrictedByTelegram : + R.string.MessageUnsupportedHint), 2500); + break; + } + case CLICK_TYPE_MESSAGE_EDITED_ICON: { + showMessageTooltip((targetView, outRect) -> { + isEditedCounterLastDrawRect.round(outRect); + outRect.top -= Screen.dp(6); + }, + Lang.getRelativeDate( + getEditDate(), TimeUnit.SECONDS, + tdlib.currentTimeMillis(), TimeUnit.MILLISECONDS, + true, 60, R.string.message_edited, false + ), + 2500); + break; + } case CLICK_TYPE_REPLY: { if (msg.replyTo != null && msg.replyTo.getConstructor() == TdApi.MessageReplyToMessage.CONSTRUCTOR) { TdApi.MessageReplyToMessage replyToMessage = (TdApi.MessageReplyToMessage) msg.replyTo; @@ -2875,6 +3005,9 @@ public boolean onTouchEvent (MessageView view, MotionEvent e) { private static final int CLICK_TYPE_AVATAR = 2; private static final int CLICK_TYPE_CHANNEL_MESSAGE_ICON = 3; private static final int CLICK_TYPE_TRANSLATE_MESSAGE_ICON = 4; + private static final int CLICK_TYPE_MESSAGE_RESTRICTED_ICON = 5; + private static final int CLICK_TYPE_MESSAGE_EDITED_ICON = 6; + private static final int CLICK_TYPE_CHANNEL_MESSAGE_SENDER_ICON = 7; private int clickType = CLICK_TYPE_NONE; @@ -3239,7 +3372,11 @@ private void layoutInfo () { } if (shouldShowEdited()) { - max -= Screen.dp(5f) + Icons.getEditedIconWidth(); + if (isBeingEdited()) { + max -= Screen.dp(6f) + Icons.getEditedIconWidth(); + } else { + max -= isEdited.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)) + Screen.dp(COUNTER_ADD_MARGIN); + } } String authorName; @@ -3250,6 +3387,13 @@ private void layoutInfo () { } max -= isPinned.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)) + Screen.dp(COUNTER_ADD_MARGIN); + if (shouldShowMessageRestrictedWarning()) { + if (isRestrictedByTelegram()) { + max -= isRestricted.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)) + Screen.dp(COUNTER_ADD_MARGIN); + } else { + max -= isUnsupported.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)) + Screen.dp(COUNTER_ADD_MARGIN); + } + } if (replyCounter.getVisibility() > 0f) { max -= replyCounter.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN)); } @@ -3398,6 +3542,10 @@ private void loadReply () { replyData.load(); } + public void onReplyLoaded () { + + } + @MessageChangeType private int performContentfulUpdate (FutureBool act) { int height = getHeight(); @@ -3844,18 +3992,28 @@ protected void drawBubbleTimePart (Canvas c, MessageView view) { isPinned.draw(c, startX, counterY, Gravity.LEFT, 1f, view, iconColorId); startX += isPinned.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); + if (shouldShowMessageRestrictedWarning()) { + if (isRestrictedByTelegram()) { + isRestricted.draw(c, startX, counterY, Gravity.LEFT, 1f, view, 0, isRestrictedCounterLastDrawRect); + startX += isRestricted.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); + } else { + isUnsupported.draw(c, startX, counterY, Gravity.LEFT, 1f, view, iconColorId, isRestrictedCounterLastDrawRect); + startX += isUnsupported.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); + } + } + if (shouldShowEdited()) { if (isBeingEdited()) { Drawables.draw(c, Icons.getClockIcon(iconColorId), startX - Screen.dp(6f), startY + Screen.dp(4.5f) - Screen.dp(5f), iconPaint); + startX += Icons.getEditedIconWidth() + Screen.dp(3f); } else { - Drawables.draw(c, view.getSparseDrawable(R.drawable.baseline_edit_12, ColorId.NONE), startX, startY + Screen.dp(4.5f), iconPaint); + isEdited.draw(c, startX, counterY, Gravity.LEFT, 1f, view, iconColorId, isEditedCounterLastDrawRect); + startX += isEdited.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN)); } - startX += Icons.getEditedIconWidth() + Screen.dp(2f); } if (translationStyleMode() == Settings.TRANSLATE_MODE_INLINE) { - isTranslatedCounter.draw(c, startX, isTranslatedCounterY = counterY, Gravity.LEFT, 1f); - isTranslatedCounterX = startX + Screen.dp(7); + isTranslatedCounter.draw(c, startX, counterY, Gravity.LEFT, 1f, isTranslatedCounterLastDrawRect); startX += isTranslatedCounter.getScaledWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN)); } @@ -3906,7 +4064,11 @@ protected final int computeBubbleTimePartWidth (boolean includePadding, boolean width = (int) U.measureText(time, mTimeBubble()); } if (shouldShowEdited()) { - width += Icons.getEditedIconWidth() + Screen.dp(2f); + if (isBeingEdited()) { + width += Icons.getEditedIconWidth() + Screen.dp(2f); + } else { + width += isEdited.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN), isTarget); + } } if (translationStyleMode() == Settings.TRANSLATE_MODE_INLINE) { width += isTranslatedCounter.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN), isTarget); @@ -3926,6 +4088,13 @@ protected final int computeBubbleTimePartWidth (boolean includePadding, boolean width += replyCounter.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN), isTarget); } width += isPinned.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN), isTarget); + if (shouldShowMessageRestrictedWarning()) { + if (isRestrictedByTelegram()) { + width += isRestricted.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN), isTarget); + } else { + width += isUnsupported.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN), isTarget); + } + } if (reactionsDrawMode == REACTIONS_DRAW_MODE_FLAT) { width += reactionsCounterDrawable.getMinimumWidth() + messageReactions.getVisibility() * Screen.dp(3); width += reactionsCounter.getScaledOrTargetWidth(Screen.dp(COUNTER_ICON_MARGIN + COUNTER_ADD_MARGIN), isTarget); @@ -5007,6 +5176,20 @@ public void markAsUnread () { flags &= ~FLAG_VIEWED; } + public int getEditDate () { + synchronized (this) { + if (combinedMessages != null && !combinedMessages.isEmpty()) { + int result = 0; + for (TdApi.Message message : combinedMessages) { + result = Math.max(result, message.editDate); + } + return result; + } + } + + return msg.editDate; + } + public boolean isEdited () { if (msg.editDate > 0) { return true; @@ -5023,6 +5206,20 @@ public boolean isEdited () { return false; } + public boolean isRestrictedByTelegram () { + synchronized (this) { + if (combinedMessages != null) { + for (TdApi.Message message : combinedMessages) { + if (!StringUtils.isEmpty(message.restrictionReason)) { + return true; + } + } + } + } + + return !StringUtils.isEmpty(msg.restrictionReason); + } + protected boolean replaceTimeWithEditTime () { return false; } @@ -7823,7 +8020,9 @@ public static TGMessage valueOf (MessagesManager context, TdApi.Message msg, TdA return new TGMessageText(context, msg, new TdApi.FormattedText(Lang.getString(R.string.DeletedMessage), null)); } if (!StringUtils.isEmpty(msg.restrictionReason) && Settings.instance().needRestrictContent()) { - TGMessageText text = new TGMessageText(context, msg, new TdApi.FormattedText(msg.restrictionReason, null)); + TGMessageText text = new TGMessageText(context, msg, new TdApi.FormattedText(msg.restrictionReason, new TdApi.TextEntity[]{ + new TdApi.TextEntity(0, msg.restrictionReason.length(), new TdApi.TextEntityTypeItalic()) + })); text.addMessageFlags(FLAG_UNSUPPORTED); return text; } @@ -8044,7 +8243,10 @@ public static TGMessage valueOf (MessagesManager context, TdApi.Message msg, TdA throw Td.unsupported(msg.content); } } - TGMessageText text = new TGMessageText(context, msg, new TdApi.FormattedText(Lang.getString(unsupportedStringRes), null)); + String unsupportedText = Lang.getString(unsupportedStringRes); + TGMessageText text = new TGMessageText(context, msg, new TdApi.FormattedText(unsupportedText, new TdApi.TextEntity[]{ + new TdApi.TextEntity(0, unsupportedText.length(), new TdApi.TextEntityTypeItalic()) + })); text.addMessageFlags(FLAG_UNSUPPORTED); return text; } catch (Throwable t) { @@ -8974,12 +9176,7 @@ private TooltipOverlayView.LocationProvider getReplyLocationProvider () { } private boolean openMessageFromChannel () { - TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()).locate((targetView, outRect) -> { - outRect.left = (int) (isChannelHeaderCounterX - Screen.dp(7)); - outRect.top = (int) (isChannelHeaderCounterY - Screen.dp(7)); - outRect.right = (int) (isChannelHeaderCounterX + Screen.dp(7)); - outRect.bottom = (int) (isChannelHeaderCounterY + Screen.dp(7)); - }); + TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()).locate((targetView, outRect) -> isChannelHeaderCounterLastDrawRect.round(outRect)); tdlib().ui().openChat(this, sender.getChatId(), new TdlibUi.ChatOpenParameters() .urlOpenParameters(new TdlibUi.UrlOpenParameters().tooltip(tooltipBuilder)).keepStack() @@ -8994,26 +9191,23 @@ private boolean needDrawChannelIconInHeader () { private TooltipOverlayView.TooltipInfo languageSelectorTooltip; private void openLanguageSelectorInlineMode () { - TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()).locate((targetView, outRect) -> { - outRect.left = (int) (isTranslatedCounterX - Screen.dp(7)); - outRect.top = (int) (isTranslatedCounterY - Screen.dp(7)); - outRect.right = (int) (isTranslatedCounterX + Screen.dp(7)); - outRect.bottom = (int) (isTranslatedCounterY + Screen.dp(7)); - }); + TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()) + .locate((targetView, outRect) -> isTranslatedCounterLastDrawRect.round(outRect)); languageSelectorTooltip = tooltipBuilder.show(this, this::showLanguageSelectorInlineMode);//.hideDelayed(3500, TimeUnit.MILLISECONDS); } private void showTranslateErrorMessageBubbleMode (String message) { - TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()).locate((targetView, outRect) -> { - outRect.left = (int) (isTranslatedCounterX - Screen.dp(7)); - outRect.top = (int) (isTranslatedCounterY - Screen.dp(7)); - outRect.right = (int) (isTranslatedCounterX + Screen.dp(7)); - outRect.bottom = (int) (isTranslatedCounterY + Screen.dp(7)); - }); + TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()) + .locate((targetView, outRect) -> isTranslatedCounterLastDrawRect.round(outRect)); languageSelectorTooltip = tooltipBuilder.show(tdlib, message).hideDelayed(3500, TimeUnit.MILLISECONDS); } + private TooltipOverlayView.TooltipInfo showMessageTooltip (TooltipOverlayView.LocationProvider locate, String message, long msDuration) { + TooltipOverlayView.TooltipBuilder tooltipBuilder = context().tooltipManager().builder(findCurrentView()).locate(locate); + return tooltipBuilder.show(tdlib, message).hideDelayed(msDuration, TimeUnit.MILLISECONDS); + } + private void showLanguageSelectorInlineMode (View v) { if (languageSelectorTooltip == null) return; @@ -9140,12 +9334,9 @@ private void setTranslatedStatus (int status, boolean animated) { private void checkSelectLanguageWarning (boolean force) { String current = mTranslationsManager.getCurrentTranslatedLanguage(); if (current == null || StringUtils.equalsOrBothEmpty(current, getOriginalMessageLanguage()) || force) { - context().tooltipManager().builder(findCurrentView()).locate((targetView, outRect) -> { - outRect.left = (int) (isTranslatedCounterX - Screen.dp(7)); - outRect.top = (int) (isTranslatedCounterY - Screen.dp(7)); - outRect.right = (int) (isTranslatedCounterX + Screen.dp(7)); - outRect.bottom = (int) (isTranslatedCounterY + Screen.dp(7)); - }).show(tdlib, Lang.getString(R.string.TapToSelectLanguage)).hideDelayed(3500, TimeUnit.MILLISECONDS);; + context().tooltipManager().builder(findCurrentView()) + .locate((targetView, outRect) -> isTranslatedCounterLastDrawRect.round(outRect)) + .show(tdlib, Lang.getString(R.string.TapToSelectLanguage)).hideDelayed(3500, TimeUnit.MILLISECONDS);; } } @@ -9192,7 +9383,7 @@ public void updateReactionAvatars (boolean animated) { } } - public boolean matchesReactionSenderAvatarFilter (TdApi.MessageReaction reaction, TdApi.MessageSender sender) { + public boolean matchesReactionSenderAvatarFilter (TdApi.FormattedText messageText, TdApi.MessageReaction reaction, TdApi.MessageSender sender) { final long currentChatId = getChatId(); if (Td.equalsTo(reaction.usedSenderId, sender)) { @@ -9206,7 +9397,7 @@ public boolean matchesReactionSenderAvatarFilter (TdApi.MessageReaction reaction long userId = Td.getSenderUserId(sender); final TdApi.User user = userId != 0 ? tdlib.cache().user(userId) : null; - if (user != null && (user.isContact || user.isCloseFriend || TD.containsMention(getTextToTranslateImpl(), user))) { + if (user != null && (user.isContact || user.isCloseFriend || TD.containsMention(messageText, user))) { return true; } @@ -9360,6 +9551,30 @@ public String getSponsoredMessageUrl () { return null; } + /* * */ + + @Nullable + public final TdApi.FormattedText getMessageText () { + synchronized (this) { + if (combinedMessages != null && !combinedMessages.isEmpty()) { + final TdApi.FormattedText sep = new TdApi.FormattedText(" ", new TdApi.TextEntity[0]); + TdApi.FormattedText result = new TdApi.FormattedText("", new TdApi.TextEntity[0]); + for (TdApi.Message msg : combinedMessages) { + final TdApi.FormattedText textPart = msg.content != null ? Td.textOrCaption(msg.content) : null; + if (!Td.isEmpty(textPart)) { + if (!Td.isEmpty(result)) { + result = Td.concat(result, sep); + } + result = Td.concat(result, textPart); + } + } + return !Td.isEmpty(result) ? result : null; + } + } + return msg.content != null ? Td.textOrCaption(msg.content) : null; + } + + /* * */ public static @EmojiMessageContentType int getEmojiMessageContentType (TdApi.MessageContent content) { diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGReactions.java b/app/src/main/java/org/thunderdog/challegram/data/TGReactions.java index 2ed2b32b78..20d77923a1 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGReactions.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGReactions.java @@ -252,7 +252,8 @@ private TdApi.MessageSender[] getRecentSenderIds (TdApi.MessageReaction reaction return sendersPreFiltered.toArray(new TdApi.MessageSender[0]); } - return ArrayUtils.filter(sendersPreFiltered, (item) -> parent.matchesReactionSenderAvatarFilter(reaction, item)).toArray(new TdApi.MessageSender[0]); + final TdApi.FormattedText msgText = parent.getMessageText(); + return ArrayUtils.filter(sendersPreFiltered, (item) -> parent.matchesReactionSenderAvatarFilter(msgText, reaction, item)).toArray(new TdApi.MessageSender[0]); } public void requestAvatarFiles (ComplexReceiver complexReceiver, boolean isUpdate) { diff --git a/app/src/main/java/org/thunderdog/challegram/filegen/PhotoGenerationInfo.java b/app/src/main/java/org/thunderdog/challegram/filegen/PhotoGenerationInfo.java index dc8dd4fea5..94f8d4df2b 100644 --- a/app/src/main/java/org/thunderdog/challegram/filegen/PhotoGenerationInfo.java +++ b/app/src/main/java/org/thunderdog/challegram/filegen/PhotoGenerationInfo.java @@ -109,6 +109,9 @@ public PaintState getPaintState () { } public boolean needSpecialProcessing (boolean needRotate) { + if (cropState != null && cropState.getFlags() != 0) { + return true; + } if (paintState != null && !paintState.isEmpty()) { return true; } @@ -241,8 +244,12 @@ public Bitmap process (Bitmap source, boolean needRotate) { int bitmapBottom = source.getHeight(); int rotation = needRotate ? this.rotation : 0; boolean drawingComplete = false; + boolean needMirrorHorizontal = false; + boolean needMirrorVertical = false; if (cropState != null) { + needMirrorHorizontal = cropState.needMirrorHorizontally(); + needMirrorVertical = cropState.needMirrorVertically(); rotation = MathUtils.modulo(rotation + cropState.getRotateBy(), 360); if (regionDecoderState == REGION_ERROR) { Log.i("Region reader failed, cropping in-memory"); @@ -279,7 +286,16 @@ public Bitmap process (Bitmap source, boolean needRotate) { if (scale != 1f) { c.scale(scale, scale, w / 2, h / 2); } + if (needMirrorHorizontal || needMirrorVertical) { + c.save(); + c.scale(needMirrorHorizontal ? -1 : 1, needMirrorVertical ? -1 : 1, w / 2f, h / 2f); + } c.drawBitmap(source, 0, 0, null); + if (needMirrorHorizontal || needMirrorVertical) { + c.restore(); + needMirrorHorizontal = false; + needMirrorVertical = false; + } if (paintState != null) { drawPaintState(c, source.getWidth(), source.getHeight()); drawingComplete = true; @@ -302,13 +318,34 @@ public Bitmap process (Bitmap source, boolean needRotate) { if (paintState != null && !drawingComplete) { Bitmap altered = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(altered); + if (needMirrorHorizontal || needMirrorVertical) { + c.save(); + c.scale(needMirrorHorizontal ? -1 : 1, needMirrorVertical ? -1 : 1, source.getWidth() / 2f, source.getHeight() / 2f); + } c.drawBitmap(source, 0, 0, null); + if (needMirrorHorizontal || needMirrorVertical) { + c.restore(); + needMirrorHorizontal = false; + needMirrorVertical = false; + } drawPaintState(c, source.getWidth(), source.getHeight()); source.recycle(); source = altered; U.recycle(c); } + if (needMirrorHorizontal || needMirrorVertical) { // fixme: use matrix ?? + Bitmap flipped = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(flipped); + + c.scale(needMirrorHorizontal ? -1 : 1, needMirrorVertical ? -1 : 1, source.getWidth() / 2f, source.getHeight() / 2f); + c.drawBitmap(source, 0, 0, null); + + source.recycle(); + source = flipped; + U.recycle(c); + } + return Bitmap.createBitmap(source, bitmapLeft, bitmapTop, bitmapRight - bitmapLeft, bitmapBottom - bitmapTop, matrix, false); } diff --git a/app/src/main/java/org/thunderdog/challegram/loader/ImageReceiver.java b/app/src/main/java/org/thunderdog/challegram/loader/ImageReceiver.java index f684a5cfca..e9e8ea3b9b 100644 --- a/app/src/main/java/org/thunderdog/challegram/loader/ImageReceiver.java +++ b/app/src/main/java/org/thunderdog/challegram/loader/ImageReceiver.java @@ -417,9 +417,20 @@ private int getCroppedHeight (int sourceHeight) { return sourceHeight; } + private int getVisualRotationWithoutCropState () { + if (file != null) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && file instanceof ImageGalleryFile && ((ImageGalleryFile) file).needThumb() ? 0 : file.getVisualRotation(); + } + return 0; + } + + private boolean needSwapMirrorAxis () { + return file != null && U.isRotated(Math.abs(getVisualRotationWithoutCropState() - file.getVisualRotation())); + } + private int getVisualRotation () { if (file != null) { - int rotation = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && file instanceof ImageGalleryFile && ((ImageGalleryFile) file).needThumb() ? 0 : file.getVisualRotation(); + int rotation = getVisualRotationWithoutCropState(); if (displayCrop != null) { rotation = MathUtils.modulo(rotation + displayCrop.getRotateBy(), 360); } @@ -1178,6 +1189,9 @@ public void draw (Canvas c) { } else { PaintState paintState = file.getPaintState(); float scaleType = file.getScaleType(); + final boolean needSwapMirrorAxis = needSwapMirrorAxis(); + final boolean needMirrorHorizontally = displayCrop != null && (needSwapMirrorAxis ? displayCrop.needMirrorVertically() : displayCrop.needMirrorHorizontally()); + final boolean needMirrorVertically = displayCrop != null && (needSwapMirrorAxis ? displayCrop.needMirrorHorizontally() : displayCrop.needMirrorVertically()); if (scaleType == ImageFile.CENTER_CROP || scaleType == ImageFile.FIT_CENTER) { // c.drawRect(left, top, right, bottom, Paints.fillingPaint(0xaa00ff00)); boolean hasCrop = displayCrop != null; @@ -1192,6 +1206,9 @@ public void draw (Canvas c) { if (left != 0 || top != 0) { c.translate(left, top); } + /*if (hasCrop && displayCrop.needMirror()) { + c.scale(displayCrop.needMirrorHorizontally() ? -1 : 1, displayCrop.needMirrorVertically() ? -1 : 1, (right - left) / 2f, (bottom - top) / 2f); + }*/ if (rotation != 0) { c.rotate(rotation, (right - left) / 2f, (bottom - top) / 2f); } @@ -1226,12 +1243,12 @@ public void draw (Canvas c) { c.rotate(degrees, cx, cy); c.scale(scale, scale, cx, cy); - drawBitmap(c, bitmap, 0, 0, paint); + drawBitmap(c, bitmap, 0, 0, needMirrorHorizontally, needMirrorVertically, paint); if (paintState != null) { paintState.draw(c, 0, 0, bitmap.getWidth(), bitmap.getHeight()); } } else { - drawBitmap(c, bitmap, bitmapRect, rect, paint); + drawBitmap(c, bitmap, bitmapRect, rect, needMirrorHorizontally, needMirrorVertically, paint); if (paintState != null) { c.clipRect(rect); DrawAlgorithms.drawPainting(c, bitmap, bitmapRect, rect, paintState); @@ -1239,7 +1256,7 @@ public void draw (Canvas c) { } } else { c.concat(bitmapMatrix); - drawBitmap(c, bitmap, 0, 0, paint); + drawBitmap(c, bitmap, 0, 0, needMirrorHorizontally, needMirrorVertically, paint); if (paintState != null) { c.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight()); paintState.draw(c, 0, 0, bitmap.getWidth(), bitmap.getHeight()); @@ -1248,7 +1265,7 @@ public void draw (Canvas c) { c.restore(); } else { - drawBitmap(c, bitmap, bitmapRect, drawRegion, paint); + drawBitmap(c, bitmap, bitmapRect, drawRegion, needMirrorHorizontally, needMirrorVertically, paint); if (paintState != null) { c.save(); c.clipRect(drawRegion); @@ -1263,18 +1280,42 @@ public interface OnCompleteListener { void onComplete (ImageReceiver receiver, ImageFile imageFile); } - private static void drawBitmap (Canvas c, Bitmap bitmap, float left, float top, Paint paint) { + private static void drawBitmap (Canvas c, Bitmap bitmap, float left, float top, boolean needMirrorHorizontally, boolean needMirrorVertically, Paint paint) { try { + c.save(); + c.scale(needMirrorHorizontally ? -1 : 1, needMirrorVertically ? -1 : 1, left + bitmap.getWidth() / 2f, top + bitmap.getHeight() / 2f); c.drawBitmap(bitmap, left, top, paint); + c.restore(); } catch (Throwable t) { Log.e(Log.TAG_IMAGE_LOADER, "Unable to draw bitmap", t); Tracer.onOtherError(t); } } - private static void drawBitmap (Canvas c, Bitmap bitmap, Rect rect, Rect drawRegion, Paint paint) { + private static final Rect tmpRect = new Rect(); + + private static void drawBitmap (Canvas c, Bitmap bitmap, Rect rect, Rect drawRegion, boolean needMirrorHorizontally, boolean needMirrorVertically, Paint paint) { try { - c.drawBitmap(bitmap, rect, drawRegion, paint); + c.save(); + c.scale(needMirrorHorizontally ? -1 : 1, needMirrorVertically ? -1 : 1, drawRegion.centerX(), drawRegion.centerY()); + tmpRect.set(rect); + + if (needMirrorHorizontally) { + int width = bitmap.getWidth(); + int left = rect.left; + int right = rect.right; + tmpRect.left = width - right; + tmpRect.right = width - left; + } + if (needMirrorVertically) { + int height = bitmap.getHeight(); + int top = rect.top; + int bottom = rect.bottom; + tmpRect.top = height - bottom; + tmpRect.bottom = height - top; + } + c.drawBitmap(bitmap, tmpRect, drawRegion, paint); + c.restore(); } catch (Throwable t) { Log.e(Log.TAG_IMAGE_LOADER, "Unable to draw bitmap", t); Tracer.onOtherError(t); diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/AvatarPickerMode.java b/app/src/main/java/org/thunderdog/challegram/mediaview/AvatarPickerMode.java new file mode 100644 index 0000000000..f60e0913dc --- /dev/null +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/AvatarPickerMode.java @@ -0,0 +1,26 @@ +/* + * This file is a part of Telegram X + * Copyright © 2014 (tgx-android@pm.me) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * File created on 19/11/2023 at 00:51 + */ +package org.thunderdog.challegram.mediaview; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +@IntDef({AvatarPickerMode.NONE, AvatarPickerMode.PROFILE, AvatarPickerMode.GROUP, AvatarPickerMode.CHANNEL}) +public @interface AvatarPickerMode { + int NONE = 0, PROFILE = 1, GROUP = 2, CHANNEL = 3; +} diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/EditButton.java b/app/src/main/java/org/thunderdog/challegram/mediaview/EditButton.java index 82156ed36f..9aae55a95e 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/EditButton.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/EditButton.java @@ -18,19 +18,25 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.drawable.Drawable; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import org.thunderdog.challegram.R; +import org.thunderdog.challegram.loader.AvatarReceiver; +import org.thunderdog.challegram.telegram.Tdlib; import org.thunderdog.challegram.tool.Drawables; import org.thunderdog.challegram.tool.Paints; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.Views; +import org.thunderdog.challegram.widget.SendButton; import me.vkryl.android.AnimatorUtils; +import me.vkryl.android.animator.BoolAnimator; import me.vkryl.android.animator.FactorAnimator; import me.vkryl.core.ColorUtils; import me.vkryl.core.MathUtils; @@ -49,6 +55,7 @@ public class EditButton extends View implements FactorAnimator.Target { public EditButton (Context context) { super(context); + avatarReceiver = new AvatarReceiver(this); setBackgroundResource(R.drawable.bg_btn_header_light); } @@ -80,6 +87,7 @@ public void setUseFastAnimations () { private static final int ACTIVE_ANIMATOR = 0; private static final int CHANGE_ANIMATOR = 1; private static final int EDITED_ANIMATOR = 2; + private static final int SLOW_MODE_VISIBILITY_ANIMATOR = 3; private float editedFactor; private FactorAnimator editedAnimator; @@ -119,6 +127,16 @@ private void setEditedFactor (float factor) { } } + private BoolAnimator slowModeVisibilityAnimator; + + public void setSlowModeVisibility (boolean visibility, boolean animated) { + if (slowModeVisibilityAnimator == null) { + slowModeVisibilityAnimator = new BoolAnimator(SLOW_MODE_VISIBILITY_ANIMATOR, this, AnimatorUtils.DECELERATE_INTERPOLATOR, useFastAnimations ? 180L : 380L, visibility); + } + + slowModeVisibilityAnimator.setValue(visibility, animated); + } + private FactorAnimator iconAnimator; private int pendingIcon; @@ -265,6 +283,10 @@ public void onFactorChanged (int id, float factor, float fraction, FactorAnimato setEditedFactor(factor); break; } + case SLOW_MODE_VISIBILITY_ANIMATOR: { + invalidate(); + break; + } } } @@ -275,10 +297,18 @@ public void onFactorChangeFinished (int id, float finalFactor, FactorAnimator ca private static final float MIN_SCALE = USE_SCALE ? .78f : 1f; private static final float STEP_FACTOR = USE_SCALE ? .45f : .5f; + + private final Path clipPath = new Path(); + + @ColorInt + private int getCurrentIconColor () { + float activeFactor = iconRes != R.drawable.baseline_volume_up_24 ? this.activeFactor : 0f; + return changer.getColor(activeFactor); + } + @Override protected void onDraw (Canvas c) { - float activeFactor = iconRes != R.drawable.baseline_volume_up_24 ? this.activeFactor : 0f; - Paint paint = Paints.getPorterDuffPaint(changer.getColor(activeFactor)); + Paint paint = Paints.getPorterDuffPaint(getCurrentIconColor()); int centerX = getMeasuredWidth() / 2; int centerY = getMeasuredHeight() / 2; @@ -300,6 +330,16 @@ protected void onDraw (Canvas c) { return; } + final float slowModeCounterFactor = slowModeVisibilityAnimator != null ? slowModeVisibilityAnimator.getFloatValue() : 1f; + final boolean needDrawSlowModeCounter = slowModeCounterController != null && slowModeCounterController.isVisible() && slowModeCounterFactor > 0f; + int clipSaveTo = -1; + if (needDrawSlowModeCounter) { + slowModeCounterController.draw(c, avatarReceiver, centerX, centerY, slowModeCounterFactor); + slowModeCounterController.buildClipPath(this, clipPath); + clipSaveTo = Views.save(c); + c.clipPath(clipPath); + } + if (alpha != 1f) { paint.setAlpha((int) (255f * alpha)); if (USE_SCALE) { @@ -345,5 +385,46 @@ protected void onDraw (Canvas c) { c.drawCircle(centerX, getMeasuredHeight() - Screen.dp(9.5f), Screen.dp(2f), Paints.fillingPaint(color)); } + + if (needDrawSlowModeCounter) { + Views.restore(c, clipSaveTo); + } + } + + private SendButton.SlowModeCounterController slowModeCounterController; + private final AvatarReceiver avatarReceiver; + + public void destroySlowModeCounterController () { + if (slowModeCounterController != null) { + slowModeCounterController.performDestroy(); + slowModeCounterController = null; + } + avatarReceiver.destroy(); + } + + @Override + protected void onAttachedToWindow () { + avatarReceiver.attach(); + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow () { + avatarReceiver.detach(); + super.onDetachedFromWindow(); + } + + public SendButton.SlowModeCounterController getSlowModeCounterController (Tdlib tdlib) { + if (slowModeCounterController != null && slowModeCounterController.tdlib() != tdlib) { + destroySlowModeCounterController(); + } + + if (slowModeCounterController == null) { + slowModeCounterController = new SendButton.SlowModeCounterController(tdlib, this, this::getCurrentIconColor, true, false, (a, b, c) -> { + avatarReceiver.requestMessageSender(a, b, c); + invalidate(); + }); + } + return slowModeCounterController; } } diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/MediaViewController.java b/app/src/main/java/org/thunderdog/challegram/mediaview/MediaViewController.java index acf2a54ce6..f1c69ab86f 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/MediaViewController.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/MediaViewController.java @@ -184,7 +184,7 @@ public class MediaViewController extends ViewController openCrop(true)); + } + } + private void onAppear () { // Nothing to do? } @@ -1247,6 +1269,11 @@ public void onFactorChangeFinished (int id, float finalFactor, FactorAnimator ca } break; } + case ANIMATOR_IMAGE_FLIP_HORIZONTALLY: + case ANIMATOR_IMAGE_FLIP_VERTICALLY: { + applyImageMirror(); + break; + } case ANIMATOR_THUMBS: { if (finalFactor == 0f) { clearThumbsView(); @@ -2939,9 +2966,9 @@ protected void onDraw (Canvas c) { private LinearLayout editButtons; private EditButton cropOrStickerButton; private EditButton paintOrMuteButton; + private EditButton mirrorButton; private EditButton adjustOrTextButton; private StopwatchHeaderButton stopwatchButton; - private @Nullable MediaLayout.SenderSendIcon senderSendIcon; private FrameLayoutFix bottomWrap; private LinearLayout captionWrapView; @@ -4646,6 +4673,7 @@ protected View onCreateView (Context context) { popupView = new PopupLayout(context); popupView.setOverlayStatusBar(true); + popupView.setShowListener(this); if (mode == MODE_SECRET) { popupView.setIgnoreHorizontal(); } @@ -4911,6 +4939,8 @@ public void getOutline (View view, android.graphics.Outline outline) { switch (mode) { case MODE_GALLERY: { + final boolean inProfilePhotoEditMode = inProfilePhotoEditMode(); + TdApi.Chat chat = getArgumentsStrict().receiverChatId != 0 ? tdlib.chat(getArgumentsStrict().receiverChatId) : null; mediaView.setOffsets(0, 0, 0, 0, 0); // Screen.dp(56f) @@ -4927,19 +4957,14 @@ public void getOutline (View view, android.graphics.Outline outline) { sendButton = new EditButton(context); sendButton.setId(R.id.btn_send); - sendButton.setIcon(R.drawable.deproko_baseline_send_24, false, false); + setDefaultSendButtonIcon(false); sendButton.setOnClickListener(this); sendButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(56f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.RIGHT)); sendButton.setBackgroundResource(R.drawable.bg_btn_header_light); - editWrap.addView(sendButton); - - if (chat != null && chat.messageSenderId != null) { - senderSendIcon = new MediaLayout.SenderSendIcon(context, tdlib(), chat.id); - senderSendIcon.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(19), Screen.dp(19), Gravity.RIGHT | Gravity.BOTTOM, 0, 0, Screen.dp(11), Screen.dp(8))); - senderSendIcon.setBackgroundColorId(getHeaderColorId()); - senderSendIcon.update(chat.messageSenderId); - editWrap.addView(senderSendIcon); + if (selectDelegate != null) { + sendButton.getSlowModeCounterController(tdlib).setCurrentChat(selectDelegate.getOutputChatId()); } + editWrap.addView(sendButton); if (chat != null) { tdlib.ui().createSimpleHapticMenu(this, chat.id, () -> currentActiveButton == 0, this::canDisableMarkdown, () -> true, hapticItems -> { @@ -4993,8 +5018,8 @@ public boolean onHapticMenuItemClick (View view, View parentView, HapticMenuHelp return true; }).bindTutorialFlag(Settings.TUTORIAL_SEND_AS_FILE)); } - if (senderSendIcon != null) { - hapticItems.add(0, senderSendIcon.createHapticSenderItem(chat).setOnClickListener((view, parentView, item) -> { + if (chat != null && chat.messageSenderId != null) { + hapticItems.add(0, MediaLayout.createHapticSenderItem(tdlib, chat).setOnClickListener((view, parentView, item) -> { openSetSenderPopup(chat); return true; })); @@ -5210,10 +5235,11 @@ public boolean onTouchEvent (MotionEvent event) { captionWrapView.addView(captionView); captionWrapView.setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM)); - bottomWrap.addView(captionWrapView); - - bottomWrap.addView(captionDoneButton); - bottomWrap.addView(captionEmojiButton); + if (!inProfilePhotoEditMode) { + bottomWrap.addView(captionWrapView); + bottomWrap.addView(captionDoneButton); + bottomWrap.addView(captionEmojiButton); + } videoSliderView = new VideoControlView(context); videoSliderView.setSliderListener(this); @@ -5321,7 +5347,6 @@ public boolean onTouchEvent (MotionEvent event) { checkView.setLayoutParams(fp); checkView.setOnClickListener(this); checkView.forceSetChecked(isCurrentItemSelected()); - contentView.addView(checkView); fp = FrameLayoutFix.newParams(ViewGroup.LayoutParams.WRAP_CONTENT, Screen.dp(30f), Gravity.RIGHT); fp.rightMargin = Screen.dp(78f); @@ -5335,9 +5360,13 @@ public boolean onTouchEvent (MotionEvent event) { int count = getSelectedMediaCount(); counterView.initCounter(Math.max(count, 1), false); forceCounterFactor(count == 0 ? 0f : 1f); - contentView.addView(counterView); - if (chat != null) { + if (!inProfilePhotoEditMode) { + contentView.addView(checkView); + contentView.addView(counterView); + } + + if (chat != null || inProfilePhotoEditMode) { fp = FrameLayoutFix.newParams(ViewGroup.LayoutParams.WRAP_CONTENT, Screen.getStatusBarHeight()); if (HeaderView.getTopOffset() > 0) { fp.leftMargin = Screen.dp(8f); @@ -5358,7 +5387,11 @@ public boolean onTouchEvent (MotionEvent event) { ImageView imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - imageView.setImageResource(R.drawable.baseline_arrow_upward_18); + imageView.setImageResource(getResId( + R.drawable.baseline_arrow_upward_18, + R.drawable.dot_baseline_account_circle_18, + R.drawable.dot_baseline_group_circle_18, + R.drawable.dot_baseline_channel_circle_18)); imageView.setColorFilter(0xffffffff); imageView.setAlpha((float) 0xaa / (float) 0xff); imageView.setLayoutParams(lp); @@ -5367,13 +5400,14 @@ public boolean onTouchEvent (MotionEvent event) { lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp.leftMargin = Screen.dp(6f); + final int textRes = getResId(0, R.string.ProfilePhoto, R.string.GroupPhoto, R.string.ChannelPhoto); TextView textView = new NoScrollTextView(context); textView.setTextColor(0xaaffffff); textView.setSingleLine(true); textView.setEllipsize(TextUtils.TruncateAt.END); textView.setTypeface(Fonts.getRobotoMedium()); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13f); - textView.setText(tdlib.chatTitle(chat)); + textView.setText(textRes != 0 ? Lang.getString(textRes) : (chat != null ? tdlib.chatTitle(chat) : null)); textView.setLayoutParams(lp); receiverView.addView(textView); @@ -5547,6 +5581,22 @@ public boolean onTouchEvent (MotionEvent event) { return contentView; } + private int getResId (int defaultResId, int profileResId, int groupResId, int channelResId) { + final int mode = getArgumentsStrict().avatarPickerMode; + if (mode == AvatarPickerMode.PROFILE) { + return profileResId; + } else if (mode == AvatarPickerMode.CHANNEL) { + return channelResId; + } else if (mode == AvatarPickerMode.GROUP) { + return groupResId; + } + return defaultResId; + } + + private boolean inProfilePhotoEditMode () { + return getArgumentsStrict().avatarPickerMode != AvatarPickerMode.NONE; + } + private int controlsMargin; private void setControlsMargin (int margin) { @@ -5623,6 +5673,9 @@ public void destroy () { if (captionView instanceof Destroyable) { ((Destroyable) captionView).performDestroy(); } + if (sendButton != null) { + sendButton.destroySlowModeCounterController(); + } subscribeToChatId(0); } @@ -6214,6 +6267,7 @@ public boolean allowSliderChanges (SliderView view) { } case SECTION_CROP: { if (cropControlsWrap == null) { + final boolean inProfilePhotoEditMode = inProfilePhotoEditMode(); FrameLayoutFix.LayoutParams params; params = FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, getSectionHeight(SECTION_CROP), Gravity.BOTTOM); @@ -6236,10 +6290,19 @@ public boolean allowSliderChanges (SliderView view) { proportionButton.setOnClickListener(this); proportionButton.setIcon(R.drawable.baseline_image_aspect_ratio_24, false, false); proportionButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(56f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.LEFT)); - cropControlsWrap.addView(proportionButton); + if (!inProfilePhotoEditMode) { + cropControlsWrap.addView(proportionButton); + } + + mirrorButton = new EditButton(context()); + mirrorButton.setId(R.id.btn_mirrorHorizontal); + mirrorButton.setOnClickListener(this); + mirrorButton.setIcon(R.drawable.dot_baseline_flip_horizontal_24, false, false); + mirrorButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(56f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.LEFT, inProfilePhotoEditMode ? 0 : Screen.dp(56), 0, 0, 0)); + cropControlsWrap.addView(mirrorButton); params = FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - params.leftMargin = Screen.dp(56f); + params.leftMargin = Screen.dp(inProfilePhotoEditMode ? 56f : (56f * 2)); params.rightMargin = Screen.dp(56f); rotationControlView = new RotationControlView(context()); @@ -6455,6 +6518,8 @@ private void setInCrop (boolean inCrop) { if (!inCrop) { prepareSectionToHide(SECTION_CROP); mediaView.setVisibility(View.VISIBLE); + } else if (inProfilePhotoEditMode()) { + setCropProportion(1, 1, false); } cropAnimator.setDuration(inCrop ? (currentCropState.isEmpty() ? CROP_OUT_DURATION : CROP_IN_DURATION) : 120l); cropAnimator.setValue(inCrop, true); @@ -6585,6 +6650,42 @@ public void onPreciseRotationChanged (float newValue) { cropTargetView.setDegreesAroundCenter(newValue); } + /**/ + + private static final int ANIMATOR_IMAGE_FLIP_HORIZONTALLY = 192; + private static final int ANIMATOR_IMAGE_FLIP_VERTICALLY = 193; + private final BoolAnimator imageFlipAnimatorHorizontally = new BoolAnimator(ANIMATOR_IMAGE_FLIP_HORIZONTALLY, this, AnimatorUtils.DECELERATE_INTERPOLATOR, 250L); + private final BoolAnimator imageFlipAnimatorVertically = new BoolAnimator(ANIMATOR_IMAGE_FLIP_VERTICALLY, this, AnimatorUtils.DECELERATE_INTERPOLATOR, 250L); + + private boolean imageMirrorAnimate (int mirrorFlag, boolean needMirror, boolean animated) { + final BoolAnimator animator = mirrorFlag == CropState.FLAG_MIRROR_HORIZONTALLY ? + imageFlipAnimatorHorizontally : imageFlipAnimatorVertically; + + if (!animator.isAnimating()) { + animator.setValue(!needMirror, false); + } else { + return false; + } + currentCropState.setFlags(BitwiseUtils.setFlag(currentCropState.getFlags(), mirrorFlag, needMirror)); + animator.setValue(needMirror, animated); + return true; + } + + private void setImageMirrorFactors () { + cropTargetView.setMirrorFactors(imageFlipAnimatorHorizontally.getFloatValue(), imageFlipAnimatorVertically.getFloatValue()); + } + + private void applyImageMirror () { + cropTargetView.setMirrorFactors(currentCropState.hasFlag(CropState.FLAG_MIRROR_HORIZONTALLY) ? 1 : 0, currentCropState.hasFlag(CropState.FLAG_MIRROR_VERTICALLY) ? 1 : 0); + } + + private void cancelImageMirrorAnimations () { + imageFlipAnimatorHorizontally.cancel(); + imageFlipAnimatorVertically.cancel(); + } + + /**/ + private CropState currentCropState; private CropState oldCropState; @@ -6615,6 +6716,7 @@ private void prepareCropLayout () { cropLayout.addView(cropTargetView); cropAreaView = new CropAreaView(context()); + cropAreaView.setProfilePhotoMode(inProfilePhotoEditMode()); cropAreaView.setRectChangeListener((left, top, right, bottom) -> { if (inCrop) { currentCropState.setRect(left, top, right, bottom); @@ -6642,6 +6744,8 @@ private void prepareCropState () { proportionButton.setActive(false, false); int cropRotation = MathUtils.modulo(this.cropRotation + (oldCropState != null ? oldCropState.getRotateBy() : 0), 360); cropTargetView.resetState(cropBitmap, cropRotation, currentCropState.getDegreesAroundCenter(), currentPaintState); + cropTargetView.setMirrorFactors(currentCropState.hasFlag(CropState.FLAG_MIRROR_HORIZONTALLY) ? 1f : 0f, currentCropState.hasFlag(CropState.FLAG_MIRROR_VERTICALLY) ? 1f : 0f); + mirrorButton.setActive(currentCropState.needMirror(), false); rotationControlView.reset(currentCropState.getDegreesAroundCenter(), false); cropAreaView.resetProportion(); cropAreaView.resetState(U.getWidth(cropBitmap, cropRotation), U.getHeight(cropBitmap, cropRotation), currentCropState.getLeft(), currentCropState.getTop(), currentCropState.getRight(), currentCropState.getBottom(), false); @@ -6677,9 +6781,20 @@ private void resetCropState () { oldCropState = null; } - private void setCropProportion (int big, int small) { - cropAreaView.setFixedProportion(big, small); - proportionButton.setActive(big != 0 && small != 0, true); + private void setCropProportion (int big, int small, boolean animated) { + cropAreaView.setFixedProportion(big, small, animated); + proportionButton.setActive(big != 0 && small != 0, animated); + } + + public int getMirrorHorizontallyFlag () { + return stack != null && stack.getCurrent() != null && stack.getCurrent().isRotated() ? + CropState.FLAG_MIRROR_VERTICALLY : CropState.FLAG_MIRROR_HORIZONTALLY; + } + + private void setMirrorHorizontally (boolean newValue) { + if (imageMirrorAnimate(getMirrorHorizontallyFlag(), newValue, true)) { + mirrorButton.setActive(newValue, true); + } } private float cropStartDegrees, cropEndDegrees; @@ -6694,6 +6809,7 @@ private void resetCrop (boolean zero) { } cancelImageRotation(); + cancelImageMirrorAnimations(); cropStartDegrees = currentCropState.getDegreesAroundCenter(); cropEndDegrees = zero || oldCropState == null ? 0 : oldCropState.getDegreesAroundCenter(); @@ -6707,6 +6823,7 @@ private void resetCrop (boolean zero) { proportionButton.setActive(false, true); resettingCrop = resetCropDegrees || rotatingByDegrees != 0; closeCropAfterReset = !zero; + setMirrorHorizontally(!zero && oldCropState != null && oldCropState.hasFlag(getMirrorHorizontallyFlag())); if (zero || oldCropState == null || oldCropState.isEmpty()) { if (cropAreaView.resetArea(resettingCrop, !zero)) { resettingCrop = true; @@ -7315,6 +7432,10 @@ private boolean allowDataChanges () { } private void changeSection (int section, int mode) { + changeSection(section, mode, false); + } + + private void changeSection (int section, int mode, boolean useFastAnimation) { if (currentSection == section || !allowDataChanges()) { return; } @@ -7354,7 +7475,7 @@ private void changeSection (int section, int mode) { } } - changeSectionImpl(section); + changeSectionImpl(section, useFastAnimation); } private void applyFiltersAsync (final int futureSection) { @@ -7372,6 +7493,10 @@ private void applyFiltersAsync (final int futureSection) { } private void changeSectionImpl (int section) { + changeSectionImpl(section, false); + } + + private void changeSectionImpl (int section, boolean useFastAnimation) { if (scheduleSectionChange(currentSection, section)) { return; } @@ -7398,10 +7523,12 @@ private void changeSectionImpl (int section) { updateIconStates(true); + final long duration = useFastAnimation ? 220L : 380L; if (sectionChangeAnimator == null) { - sectionChangeAnimator = new FactorAnimator(ANIMATOR_SECTION, this, AnimatorUtils.LINEAR_INTERPOLATOR, 380l); + sectionChangeAnimator = new FactorAnimator(ANIMATOR_SECTION, this, AnimatorUtils.LINEAR_INTERPOLATOR, duration); } else { sectionChangeAnimator.forceFactor(0f); + sectionChangeAnimator.setDuration(duration); } sectionChangeAnimator.animateTo(1f); } @@ -7453,12 +7580,23 @@ private void fillIcons (int section) { if (activeButtonId != 0) { backButton.setIcon(R.drawable.baseline_close_24, true, false); sendButton.setIcon(R.drawable.baseline_check_24, true, false); + sendButton.setSlowModeVisibility(false, true); } else { backButton.setIcon(R.drawable.baseline_arrow_back_24, true, false); - sendButton.setIcon(R.drawable.deproko_baseline_send_24, true, false); + setDefaultSendButtonIcon(true); } } + private void setDefaultSendButtonIcon (boolean animated) { + sendButton.setIcon(getResId( + R.drawable.deproko_baseline_send_24, + R.drawable.dot_baseline_profile_accept_24, + R.drawable.dot_baseline_group_accept_24, + R.drawable.dot_baseline_channel_accept_24 + ), animated, false); + sendButton.setSlowModeVisibility(true, animated); + } + private boolean hasAppliedFilters () { FiltersState state = stack.getCurrent().getFiltersState(); return state != null && !state.isEmpty(); @@ -7768,9 +7906,13 @@ private void openPaintCanvas () { } private void openCrop () { + openCrop(false); + } + + private void openCrop (boolean useFastAnimation) { if (Config.CROP_ENABLED) { if (currentSection != SECTION_CROP) { - changeSection(SECTION_CROP, MODE_OK); + changeSection(SECTION_CROP, MODE_OK, useFastAnimation); } } else { // UI.showToast(R.string.FeatureDisabled, Toast.LENGTH_SHORT); @@ -7890,6 +8032,8 @@ public void onClick (View v) { changeSection(SECTION_CAPTION, MODE_OK); } else if (inputView != null && !tdlib.isSelfChat(getOutputChatId()) && !tdlib.hasPremium() && inputView.hasOnlyPremiumFeatures()) { context().tooltipManager().builder(sendButton).show(tdlib, Strings.buildMarkdown(this, Lang.getString(R.string.MessageContainsPremiumFeatures), null)).hideDelayed(); + } else if (needShowCropSectionInsteadSend()) { + changeSection(SECTION_CROP, MODE_OK); } else { send(v, Td.newSendOptions(), false, false); } @@ -7905,8 +8049,10 @@ public void onClick (View v) { } } else if (viewId == R.id.btn_rotate) { rotateBy90Degrees(); + } else if (viewId == R.id.btn_mirrorHorizontal) { + setMirrorHorizontally(!currentCropState.hasFlag(getMirrorHorizontallyFlag())); } else if (viewId == R.id.btn_proportion) { - if (allowDataChanges() && currentSection == SECTION_CROP) { + if (allowDataChanges() && currentSection == SECTION_CROP && !inProfilePhotoEditMode()) { IntList ids = new IntList(PROPORTION_MODES.length + 2); StringList strings = new StringList(PROPORTION_MODES.length + 2); IntList icons = new IntList(PROPORTION_MODES.length + 2); @@ -7985,11 +8131,11 @@ public void onClick (View v) { if (id == R.id.btn_crop_reset) { resetCrop(true); } else if (id == R.id.btn_proportion_free) { - setCropProportion(0, 0); + setCropProportion(0, 0, true); } else if (id == R.id.btn_proportion_original) { int targetWidth = cropAreaView.getTargetWidth(); int targetHeight = cropAreaView.getTargetHeight(); - setCropProportion(Math.max(targetWidth, targetHeight), Math.min(targetWidth, targetHeight)); + setCropProportion(Math.max(targetWidth, targetHeight), Math.min(targetWidth, targetHeight), true); } else { int[] mode = null; for (int[] proportionMode : PROPORTION_MODES) { @@ -7999,7 +8145,7 @@ public void onClick (View v) { } } if (mode != null) { - setCropProportion(mode[0], mode[1]); + setCropProportion(mode[0], mode[1], true); } } return true; @@ -8043,6 +8189,24 @@ public void onClick (View v) { } } + private boolean needShowCropSectionInsteadSend () { + if (!inProfilePhotoEditMode()) { + return false; + } + + if (cropAreaView == null) { + return true; + } + + final CropState cropState = obtainCropState(true); + + double targetWidth = (cropAreaView.getTargetWidth() * (cropState.getRight() - cropState.getLeft())); + double targetHeight = (cropAreaView.getTargetHeight() * (cropState.getBottom() - cropState.getTop())); + double proportion = Math.max(targetWidth, targetHeight) / Math.min(targetWidth, targetHeight); + + return Math.abs(proportion - 1d) > 0.02d; + } + // TTL private void showTTLOptions () { @@ -8175,6 +8339,10 @@ public void send (View view, TdApi.MessageSendOptions initialSendOptions, boolea return; } + if (initialSendOptions.schedulingState == null && showSlowModeRestriction(sendButton)) { + return; + } + if (initialSendOptions.schedulingState == null && getArgumentsStrict().areOnlyScheduled) { tdlib.ui().showScheduleOptions(this, getOutputChatId(), false, (modifiedSendOptions, disableMarkdown1) -> { send(view, modifiedSendOptions, disableMarkdown, asFiles); @@ -8488,11 +8656,7 @@ private void openSetSenderPopup (TdApi.Chat chat) { } private void setNewMessageSender (TdApi.Chat chat, TdApi.ChatMessageSender sender) { - tdlib().send(new TdApi.SetChatMessageSender(chat.id, sender.sender), tdlib.typedOkHandler(() -> { - if (senderSendIcon != null) { - senderSendIcon.update(chat.messageSenderId); - } - })); + tdlib().send(new TdApi.SetChatMessageSender(chat.id, sender.sender), tdlib.typedOkHandler()); } private void setEmojiShown (boolean emojiShown) { @@ -8545,4 +8709,18 @@ private void closeTextFormattingKeyboard () { public @DrawableRes int getTargetIcon () { return (textInputHasSelection || (textFormattingVisible && emojiShown)) ? R.drawable.baseline_format_text_24 : R.drawable.deproko_baseline_insert_emoticon_26; } + + public boolean showSlowModeRestriction (View v) { + if (selectDelegate == null) { + return false; + } + + CharSequence restriction = tdlib().getSlowModeRestrictionText(selectDelegate.getOutputChatId()); + if (restriction != null) { + context().tooltipManager().builder(v).show(tdlib, restriction).hideDelayed(); + return true; + } + + return false; + } } diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropAreaView.java b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropAreaView.java index 9cc7241698..4d0a52d4b5 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropAreaView.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropAreaView.java @@ -18,7 +18,9 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Rect; +import android.os.Build; import android.view.MotionEvent; import android.view.View; @@ -30,6 +32,7 @@ import org.thunderdog.challegram.tool.Paints; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.UI; +import org.thunderdog.challegram.tool.Views; import me.vkryl.android.AnimatorUtils; import me.vkryl.android.animator.BoolAnimator; @@ -78,6 +81,12 @@ public void setRotateModeChangeListener (@Nullable RotateModeChangeListener rota this.rotateModeChangeListener = rotateModeChangeListener; } + private boolean profilePhotoMode; // + + public void setProfilePhotoMode (boolean profilePhotoMode) { + this.profilePhotoMode = profilePhotoMode; + } + private static final int OUTER_LINE_COLOR = 0x99ffffff; private static final int CORNER_COLOR = 0xffffffff; private static final int INNER_LINE_COLOR = 0xeeffffff; @@ -241,6 +250,7 @@ private void checkPivotCoordinates () { private final Rect srcRect = new Rect(); private final Rect dstRect = new Rect(); + private final Path roundClipPath = new Path(); @Override protected void onDraw (Canvas c) { @@ -290,6 +300,9 @@ protected void onDraw (Canvas c) { int top = dstRect.top; int right = dstRect.right; int bottom = dstRect.bottom; + int centerX = dstRect.centerX(); + int centerY = dstRect.centerY(); + int radius = Math.min(dstRect.width(), dstRect.height()) / 2; int cornerWidth = Screen.dp(2f); int cornerHeight = Screen.dp(16f); @@ -307,6 +320,20 @@ protected void onDraw (Canvas c) { // bottom line c.drawRect(left + cornerHeight - cornerWidth, bottom, right - cornerHeight + cornerWidth, bottom + normalWidth, Paints.fillingPaint(OUTER_LINE_COLOR)); + if (profilePhotoMode) { + roundClipPath.reset(); + roundClipPath.addRect(dstRect.left, dstRect.top, dstRect.right, dstRect.bottom, Path.Direction.CW); + roundClipPath.addCircle(centerX, centerY, radius, Path.Direction.CCW); + roundClipPath.close(); + + final int s = Views.save(c); + c.clipPath(roundClipPath); + c.drawRect(dstRect, Paints.fillingPaint(ColorUtils.alphaColor(Color.alpha(overlayColor) / 300f, overlayColor))); + Views.restore(c, s); + + c.drawCircle(centerX, centerY, radius, Paints.strokeSmallPaint(OUTER_LINE_COLOR)); + } + if (cornerHeight > 0) { float cornerAlpha = mode == MODE_NORMAL || mode == MODE_INVISIBLE ? 1f - activeFactor : 1f; int cornerColor = ColorUtils.fromToArgb(OUTER_LINE_COLOR, CORNER_COLOR, cornerAlpha); @@ -785,7 +812,7 @@ private void cancelPositionAnimator () { } } - private void normalizeProportion () { + private void normalizeProportion (boolean animated) { float heightProportion = getProportion(); if (heightProportion == 0) { cancelPositionAnimator(); @@ -836,7 +863,11 @@ private void normalizeProportion () { newRight = 1.0; } - animateArea(newLeft, newTop, newRight, newBottom, false, false); + if (animated) { + animateArea(newLeft, newTop, newRight, newBottom, false, false); + } else { + setArea(newLeft, newTop, newRight, newBottom, true); + } } public boolean resetArea (boolean forceAnimation, boolean useFastAnimation) { @@ -903,11 +934,11 @@ public int getTargetHeight () { return targetHeight; } - public void setFixedProportion (int big, int small) { + public void setFixedProportion (int big, int small, boolean animated) { if (this.proportionBig != big || this.proportionSmall != small) { this.proportionBig = big; this.proportionSmall = small; - normalizeProportion(); + normalizeProportion(animated); } } diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropState.java b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropState.java index f86609d4c0..4bbe0a69ae 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropState.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropState.java @@ -186,7 +186,10 @@ public void setDegreesAroundCenter (float degreesAroundCenter) { } public void setFlags (int flags) { - this.flags = flags; + if (this.flags != flags) { + this.flags = flags; + invokeCallbacks(false); + } } public double getLeft () { diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropTargetView.java b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropTargetView.java index 522eafd611..6a3d7037d5 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropTargetView.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/crop/CropTargetView.java @@ -160,10 +160,18 @@ protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { } } + private float mirrorHorizontallyFactor = 0; + private float mirrorVerticallyFactor = 0; + + public void setMirrorFactors (float mirrorHorizontallyFactor, float mirrorVerticallyFactor) { + this.mirrorHorizontallyFactor = mirrorHorizontallyFactor; + this.mirrorVerticallyFactor = mirrorVerticallyFactor; + invalidate(); + } + + @Override protected void onDraw (Canvas c) { - // c.drawColor(0xffff0000); - int cx = getMeasuredWidth() / 2; int cy = getMeasuredHeight() / 2; @@ -174,7 +182,7 @@ protected void onDraw (Canvas c) { c.scale(rotationScale, rotationScale, cx, cy); } - DrawAlgorithms.drawScaledBitmap(getMeasuredWidth(), getMeasuredHeight(), c, bitmap, rotation, paintState); + DrawAlgorithms.drawScaledBitmap(getMeasuredWidth(), getMeasuredHeight(), c, bitmap, rotation, mirrorHorizontallyFactor, mirrorVerticallyFactor, paintState); if (saved) { c.restore(); diff --git a/app/src/main/java/org/thunderdog/challegram/mediaview/gl/EGLEditorView.java b/app/src/main/java/org/thunderdog/challegram/mediaview/gl/EGLEditorView.java index a7990878df..0410a2f330 100644 --- a/app/src/main/java/org/thunderdog/challegram/mediaview/gl/EGLEditorView.java +++ b/app/src/main/java/org/thunderdog/challegram/mediaview/gl/EGLEditorView.java @@ -168,6 +168,7 @@ public boolean onSurfaceTextureDestroyed (SurfaceTexture surface) { @Override public void onSurfaceTextureUpdated (SurfaceTexture surface) { } }); + textureView.setScaleX(-1); contentWrap.addView(textureView); break; } @@ -219,12 +220,20 @@ private void applyCurrentStyles () { float scale = Math.max(W / w, H / h); contentWrap.setScaleX(scale); contentWrap.setScaleY(scale); + if (textureView != null) { + textureView.setScaleX(sourceCropState.needMirrorHorizontally() ? -1 : 1); + textureView.setScaleY(sourceCropState.needMirrorVertically() ? -1 : 1); + } } else { contentWrap.setRotation(0); contentWrap.setScaleX(1f); contentWrap.setScaleY(1f); contentWrap.setTranslationX(0f); contentWrap.setTranslationY(0f); + if (textureView != null) { + textureView.setScaleX(1); + textureView.setScaleY(1); + } } textureWrap.setRotation(sourceRotation); } diff --git a/app/src/main/java/org/thunderdog/challegram/navigation/EditHeaderView.java b/app/src/main/java/org/thunderdog/challegram/navigation/EditHeaderView.java index d1d34828bc..752a887653 100644 --- a/app/src/main/java/org/thunderdog/challegram/navigation/EditHeaderView.java +++ b/app/src/main/java/org/thunderdog/challegram/navigation/EditHeaderView.java @@ -16,6 +16,7 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.InputFilter; @@ -31,6 +32,7 @@ import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.loader.ImageFile; import org.thunderdog.challegram.loader.ImageFileLocal; +import org.thunderdog.challegram.loader.ImageGalleryFile; import org.thunderdog.challegram.loader.ImageReceiver; import org.thunderdog.challegram.tool.Drawables; import org.thunderdog.challegram.tool.Keyboard; @@ -51,6 +53,7 @@ public class EditHeaderView extends FrameLayoutFix implements RtlCheckListener, private final ViewController parent; private HeaderEditText input; private final ImageReceiver receiver; + private final Path clipPath = new Path(); private final Drawable icon; @@ -61,7 +64,8 @@ public EditHeaderView (Context context, ViewController parent) { setWillNotDraw(false); - receiver = new ImageReceiver(this, Screen.dp(30.5f)); + receiver = new ImageReceiver(this, 0); + receiver.prepareToBeCropped(); FrameLayoutFix.LayoutParams params; @@ -125,10 +129,18 @@ public void onTextChanged (CharSequence s, int start, int before, int count) { performReadyCallback(s.toString().trim().length() > 0); } + private Runnable onPhotoClickListener; + + public void setOnPhotoClickListener (Runnable onPhotoClickListener) { + this.onPhotoClickListener = onPhotoClickListener; + } + private void onPhotoClicked () { if (input.isEnabled()) { Keyboard.hide(input); - parent.tdlib().ui().showChangePhotoOptions(parent, file != null); + if (onPhotoClickListener != null) { + onPhotoClickListener.run(); + } ViewUtils.onClick(this); } } @@ -247,14 +259,20 @@ private void layoutReceiver () { avatarLeft = getMeasuredWidth() - avatarLeft - avatarSize; } - receiver.setRadius(avatarRadius); + // receiver.setRadius(avatarRadius); receiver.setBounds(avatarLeft, 0, avatarLeft + avatarSize, avatarSize); + clipPath.reset(); + clipPath.addCircle(receiver.centerX(), receiver.centerY(), avatarSize / 2f, Path.Direction.CW); + clipPath.close(); } @Override protected void onDraw (Canvas c) { layoutReceiver(); + int s = Views.save(c); + c.clipPath(clipPath); receiver.draw(c); + Views.restore(c, s); int cx = receiver.centerX(); int cy = receiver.centerY(); c.drawCircle(cx, cy, avatarRadius, Paints.fillingPaint(0x20000000)); @@ -265,22 +283,14 @@ public void setInputEnabled (boolean enabled) { input.setEnabled(enabled); } - private ImageFile file; + private ImageGalleryFile file; - public void setPhoto (ImageFile file) { + public void setPhoto (ImageGalleryFile file) { this.file = file; receiver.requestFile(file); } - public boolean isPhotoChanged (boolean changedIfNull) { - return (file == null && changedIfNull) || (file != null && file instanceof ImageFileLocal); - } - - public String getPhoto () { - return file == null || !(file instanceof ImageFileLocal) ? null : ((ImageFileLocal) file).getPath(); - } - - public ImageFile getImageFile () { + public ImageGalleryFile getImageFile () { return file; } diff --git a/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java b/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java index 5782d4d71b..64d8987ea6 100644 --- a/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java +++ b/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java @@ -68,6 +68,10 @@ import org.thunderdog.challegram.core.Background; import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.data.TD; +import org.thunderdog.challegram.mediaview.AvatarPickerMode; +import org.thunderdog.challegram.mediaview.MediaSelectDelegate; +import org.thunderdog.challegram.mediaview.MediaSendDelegate; +import org.thunderdog.challegram.mediaview.MediaViewDelegate; import org.thunderdog.challegram.support.RippleSupport; import org.thunderdog.challegram.support.ViewSupport; import org.thunderdog.challegram.telegram.Tdlib; @@ -3332,6 +3336,11 @@ public static class CameraOpenOptions { public CameraController.QrCodeListener qrCodeListener; public @StringRes int qrModeSubtitle; public boolean qrModeDebug; + public @AvatarPickerMode int avatarPickerMode; + + public MediaViewDelegate delegate; + public MediaSelectDelegate selectDelegate; + public MediaSendDelegate sendDelegate; public CameraOpenOptions anchor (View anchorView) { this.anchorView = anchorView; @@ -3373,6 +3382,18 @@ public CameraOpenOptions allowSystem (boolean allowSystem) { return this; } + public CameraOpenOptions setAvatarPickerMode (@AvatarPickerMode int avatarPickerMode) { + this.avatarPickerMode = avatarPickerMode; + return this; + } + + public CameraOpenOptions setMediaEditorDelegates (MediaViewDelegate delegate, MediaSelectDelegate selectDelegate, MediaSendDelegate sendDelegate) { + this.delegate = delegate; + this.selectDelegate = selectDelegate; + this.sendDelegate = sendDelegate; + return this; + } + public CameraOpenOptions optionalMicrophone (boolean optionalMicrophone) { this.optionalMicrophone = optionalMicrophone; return this; diff --git a/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java b/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java index 709c1e09b7..9ffb8156b8 100644 --- a/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java +++ b/app/src/main/java/org/thunderdog/challegram/player/RecordAudioVideoController.java @@ -62,6 +62,7 @@ import org.thunderdog.challegram.util.HapticMenuHelper; import org.thunderdog.challegram.widget.CircleFrameLayout; import org.thunderdog.challegram.widget.NoScrollTextView; +import org.thunderdog.challegram.widget.SendButton; import org.thunderdog.challegram.widget.ShadowView; import org.thunderdog.challegram.widget.SimpleVideoPlayer; import org.thunderdog.challegram.widget.VideoTimelineView; @@ -111,7 +112,8 @@ public class RecordAudioVideoController implements private CircleFrameLayout videoLayout; private View videoPlaceholderView; private RoundProgressView progressView; - private ImageView deleteButton, sendButton; + private SendButton sendButton; + private ImageView deleteButton; private HapticMenuHelper sendHelper; private VideoTimelineView videoTimelineView; private SimpleVideoPlayer videoPreviewView; @@ -159,7 +161,6 @@ private void updateColors () { this.cancelView.setTextColor(Theme.getColor(ColorId.textNeutral)); this.videoPlaceholderView.setBackgroundColor(Theme.fillingColor()); this.deleteButton.setColorFilter(Theme.iconColor()); - this.sendButton.setColorFilter(Theme.chatSendButtonColor()); this.videoBackgroundView.setBackgroundColor(Theme.getColor(ColorId.previewBackground)); this.cornerView.invalidate(); @@ -421,17 +422,17 @@ public boolean onTouchEvent (MotionEvent event) { this.deleteButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(56f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.LEFT)); this.inputOverlayView.addView(deleteButton); - this.sendButton = new ImageView(context) { + this.sendButton = new SendButton(context, R.drawable.deproko_baseline_send_24) { @Override public boolean onTouchEvent (MotionEvent event) { return editFactor > 0f && Views.isValid(this) && super.onTouchEvent(event); } }; - this.sendButton.setScaleType(ImageView.ScaleType.CENTER); - this.sendButton.setImageResource(R.drawable.deproko_baseline_send_24); Views.setClickable(sendButton); this.sendButton.setOnClickListener(v -> { - sendVideo(Td.newSendOptions()); + if (!targetController.showSlowModeRestriction(v, null)) { + sendVideo(Td.newSendOptions()); + } }); this.sendButton.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(55f), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.RIGHT)); this.inputOverlayView.addView(sendButton); @@ -667,6 +668,7 @@ private void resetViews () { videoTimelineView.performDestroy(); videoPreviewView.setMuted(true); videoPreviewView.setPlaying(true); + sendButton.destroySlowModeCounterController(); setReleased(false, false); resetState(); } @@ -996,7 +998,8 @@ private boolean canSendRecording () { } public boolean finishRecording (boolean needPreview) { - return stopRecording(canSendRecording() ? (needPreview ? CLOSE_MODE_PREVIEW : (hasValidOutputTarget() && targetController.areScheduledOnly()) ? CLOSE_MODE_PREVIEW_SCHEDULE : CLOSE_MODE_SEND) : CLOSE_MODE_CANCEL, true); + boolean forcePreview = tdlib.getSlowModeRestrictionText(targetChatId) != null; + return stopRecording(canSendRecording() ? (needPreview || forcePreview ? CLOSE_MODE_PREVIEW : (hasValidOutputTarget() && targetController.areScheduledOnly()) ? CLOSE_MODE_PREVIEW_SCHEDULE : CLOSE_MODE_SEND) : CLOSE_MODE_CANCEL, true); } private boolean stopRecording (int closeMode, boolean showPrompt) { @@ -1017,6 +1020,9 @@ private boolean stopRecording (int closeMode, boolean showPrompt) { if (async) { mode = RECORD_MODE_VIDEO_EDIT; editAnimator.setValue(true, false); + if (sendButton != null) { + sendButton.getSlowModeCounterController(tdlib).setCurrentChat(targetChatId); + } } if (recordingVideo && (closeMode == CLOSE_MODE_PREVIEW || closeMode == CLOSE_MODE_PREVIEW_SCHEDULE)) { @@ -1674,6 +1680,6 @@ private void closeVideoEditMode (@NonNull TdApi.MessageSendOptions initialSendOp } videoPreviewView.setPlaying(false); editAnimator.setValue(false, true); - + sendButton.destroySlowModeCounterController(); } } diff --git a/app/src/main/java/org/thunderdog/challegram/player/RoundVideoRecorder.java b/app/src/main/java/org/thunderdog/challegram/player/RoundVideoRecorder.java index b7f366cfb3..e889fadbc8 100644 --- a/app/src/main/java/org/thunderdog/challegram/player/RoundVideoRecorder.java +++ b/app/src/main/java/org/thunderdog/challegram/player/RoundVideoRecorder.java @@ -1446,7 +1446,7 @@ public Surface getInputSurface() { } private void didWriteData(File file, boolean last) { - if (videoConvertFirstWrite) { + if (videoConvertFirstWrite && !last) { videoConvertFirstWrite = false; } else if (last) { dispatchVideoRecordFinished(workingKey, file.length(), SystemClock.uptimeMillis() - recordStartTime, TimeUnit.MILLISECONDS); diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java index 7a7bab813f..4b764b00bd 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java @@ -63,6 +63,7 @@ import org.thunderdog.challegram.theme.Theme; import org.thunderdog.challegram.tool.Strings; import org.thunderdog.challegram.tool.UI; +import org.thunderdog.challegram.ui.EditRightsController; import org.thunderdog.challegram.unsorted.Passcode; import org.thunderdog.challegram.unsorted.Settings; import org.thunderdog.challegram.util.AppInstallationUtil; @@ -2897,14 +2898,6 @@ public boolean canAddToOtherChat (TdApi.Chat chat) { return false; } - /*public boolean hasWritePermission (long chatId) { - return chatId != 0 && hasWritePermission(chat(chatId)); - } - - public boolean hasWritePermission (TdApi.Chat chat) { - return getRestrictionStatus(chat, R.id.right_sendMessages) == null; - }*/ - public @Nullable TdApi.SecretChat chatToSecretChat (long chatId) { int secretChatId = ChatId.toSecretChatId(chatId); return secretChatId != 0 ? cache().secretChat(secretChatId) : null; @@ -10637,6 +10630,10 @@ public boolean canRestrictMembers (long chatId) { return false; } + public boolean inSlowMode (long chatId) { + return cache.getSlowModeDelayExpiresIn(ChatId.toSupergroupId(chatId), TimeUnit.SECONDS) > 0; + } + public boolean canEditSlowMode (long chatId) { if (canRestrictMembers(chatId)) { TdApi.Supergroup supergroup = chatToSupergroup(chatId); @@ -10914,6 +10911,28 @@ public RestrictionStatus getRestrictionStatus (TdApi.Chat chat, @RightId int rig return null; } + public CharSequence getSlowModeRestrictionText (long chatId) { + return getSlowModeRestrictionText(chatId, null); + } + + public CharSequence getSlowModeRestrictionText (long chatId, @Nullable TdApi.MessageSchedulingState schedulingState) { + if (schedulingState != null) { + return null; + } + + final int timeToSend = (int) cache().getSlowModeDelayExpiresIn(ChatId.toSupergroupId(chatId), TimeUnit.SECONDS); + if (timeToSend == 0) { + return null; + } + + final int minutes = timeToSend / 60; + final int seconds = timeToSend % 60; + + return (minutes > 0) ? + Lang.pluralBold(R.string.xSlowModeRestrictionMinutes, minutes): + Lang.pluralBold(R.string.xSlowModeRestrictionSeconds, seconds); + } + public CharSequence getRestrictionText (TdApi.Chat chat, TdApi.Message message) { if (message != null) { switch (message.content.getConstructor()) { @@ -11044,9 +11063,12 @@ public CharSequence getRestrictionText (TdApi.Chat chat, TdApi.InputMessageConte case TdApi.InputMessageText.CONSTRUCTOR: case TdApi.InputMessageVenue.CONSTRUCTOR: case TdApi.InputMessageContact.CONSTRUCTOR: + case TdApi.InputMessageStory.CONSTRUCTOR: return getBasicMessageRestrictionText(chat); + default: + Td.assertInputMessageContent_4e99a3f(); + throw Td.unsupported(content); } - throw new UnsupportedOperationException(content.toString()); } // Assuming if null is passed, we want to check if we can write text messages return getBasicMessageRestrictionText(chat); @@ -11296,6 +11318,22 @@ public boolean canSendBasicMessage (long chatId) { public boolean canSendBasicMessage (TdApi.Chat chat) { return canSendMessage(chat, RightId.SEND_BASIC_MESSAGES); } + + public boolean canSendSendSomeMedia (TdApi.Chat chat) { + return canSendSendSomeMedia(chat, false); + } + + public boolean canSendSendSomeMedia (TdApi.Chat chat, boolean checkGlobal) { + for (int rightId : EditRightsController.SEND_MEDIA_RIGHT_IDS) { + Tdlib.RestrictionStatus restrictionStatus = getRestrictionStatus(chat, rightId); + if (restrictionStatus == null || checkGlobal && !restrictionStatus.isGlobal()) { + return true; + } + } + + return false; + } + public boolean canSendMessage (TdApi.Chat chat, @RightId int kindResId) { switch (kindResId) { case RightId.SEND_BASIC_MESSAGES: diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java index 848d2d8a4c..488448bb58 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java @@ -55,6 +55,7 @@ import me.vkryl.core.ArrayUtils; import me.vkryl.core.collection.LongSparseIntArray; +import me.vkryl.core.collection.LongSparseLongArray; import me.vkryl.core.lambda.CancellableRunnable; import me.vkryl.core.lambda.RunnableData; import me.vkryl.core.reference.ReferenceIntMap; @@ -125,6 +126,7 @@ public interface ChatMemberStatusChangeListener { private final HashMap supergroups = new HashMap<>(); private final HashMap supergroupsFulls = new HashMap<>(); + private final LongSparseLongArray supergroupsFullsLastUpdateTime = new LongSparseLongArray(); private final ReferenceList supergroupsGlobalListeners = new ReferenceList<>(); private final ReferenceLongMap supergroupListeners = new ReferenceLongMap<>(); @@ -1286,6 +1288,19 @@ public TdApi.SupergroupFullInfo supergroupFull (long supergroupId, boolean allow return result; } + public long getSlowModeDelayExpiresIn (long supergroupId, TimeUnit timeUnit) { + synchronized (dataLock) { + final long lastUpdateTime = supergroupsFullsLastUpdateTime.get(supergroupId, 0); + final TdApi.SupergroupFullInfo supergroupFullInfo = supergroupsFulls.get(supergroupId); + if (supergroupFullInfo != null) { + final long delayExpiresInMillis = TimeUnit.SECONDS.toMillis((long) supergroupFullInfo.slowModeDelayExpiresIn); + return timeUnit.convert(Math.max(0, delayExpiresInMillis - (SystemClock.uptimeMillis() - lastUpdateTime)), TimeUnit.MILLISECONDS); + } + } + + return 0; + } + public void supergroupFull (long supergroupId, RunnableData callback) { if (supergroupId == 0) { if (callback != null) { @@ -1898,6 +1913,7 @@ private int putSupergroup (TdApi.Supergroup supergroup) { private boolean putSupergroupFull (long supergroupId, TdApi.SupergroupFullInfo supergroupFull) { supergroupsFulls.put(supergroupId, supergroupFull); + supergroupsFullsLastUpdateTime.put(supergroupId, SystemClock.uptimeMillis()); return true; } diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java index 3f5372cec9..d1d086239e 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java @@ -14,6 +14,7 @@ */ package org.thunderdog.challegram.telegram; +import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; @@ -27,6 +28,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -51,9 +53,10 @@ import org.thunderdog.challegram.MainActivity; import org.thunderdog.challegram.R; import org.thunderdog.challegram.U; +import org.thunderdog.challegram.component.MediaCollectorDelegate; +import org.thunderdog.challegram.component.attach.MediaLayout; import org.thunderdog.challegram.component.base.SettingView; import org.thunderdog.challegram.component.chat.MessagesManager; -import org.thunderdog.challegram.component.dialogs.ChatView; import org.thunderdog.challegram.component.popups.ModernActionedLayout; import org.thunderdog.challegram.component.preview.PreviewLayout; import org.thunderdog.challegram.component.sticker.StickerSetWrap; @@ -61,14 +64,16 @@ import org.thunderdog.challegram.core.Background; import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.core.LangUtils; +import org.thunderdog.challegram.core.Media; import org.thunderdog.challegram.data.TD; import org.thunderdog.challegram.data.TGBotStart; import org.thunderdog.challegram.data.TGMessage; import org.thunderdog.challegram.data.TGSwitchInline; import org.thunderdog.challegram.data.ThreadInfo; +import org.thunderdog.challegram.filegen.PhotoGenerationInfo; import org.thunderdog.challegram.filegen.SimpleGenerationInfo; -import org.thunderdog.challegram.loader.ImageFile; -import org.thunderdog.challegram.loader.ImageFileLocal; +import org.thunderdog.challegram.loader.ImageGalleryFile; +import org.thunderdog.challegram.mediaview.AvatarPickerMode; import org.thunderdog.challegram.mediaview.MediaViewController; import org.thunderdog.challegram.navigation.EditHeaderView; import org.thunderdog.challegram.navigation.HeaderView; @@ -78,6 +83,7 @@ import org.thunderdog.challegram.navigation.SettingsWrapBuilder; import org.thunderdog.challegram.navigation.TooltipOverlayView; import org.thunderdog.challegram.navigation.ViewController; +import org.thunderdog.challegram.support.RippleSupport; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.PropertyId; import org.thunderdog.challegram.theme.Theme; @@ -93,6 +99,7 @@ import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.Strings; import org.thunderdog.challegram.tool.UI; +import org.thunderdog.challegram.tool.Views; import org.thunderdog.challegram.ui.ChatJoinRequestsController; import org.thunderdog.challegram.ui.ChatLinkMembersController; import org.thunderdog.challegram.ui.ChatLinksController; @@ -135,6 +142,7 @@ import org.thunderdog.challegram.util.CustomTypefaceSpan; import org.thunderdog.challegram.util.HapticMenuHelper; import org.thunderdog.challegram.util.OptionDelegate; +import org.thunderdog.challegram.util.Permissions; import org.thunderdog.challegram.util.StringList; import org.thunderdog.challegram.widget.CheckBoxView; import org.thunderdog.challegram.widget.ForceTouchView; @@ -162,6 +170,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import me.vkryl.android.widget.FrameLayoutFix; import me.vkryl.core.ArrayUtils; import me.vkryl.core.BitwiseUtils; import me.vkryl.core.ColorUtils; @@ -1562,97 +1571,7 @@ public Object getShareItem () { context.context().navigation().navigateTo(c); } - // Change photo - - public void showChangePhotoOptions (ViewController context, boolean canDelete) { - if (canDelete) { - context.showOptions(null, new int[] {R.id.btn_changePhotoCamera, R.id.btn_changePhotoGallery, R.id.btn_changePhotoDelete}, new String[] {Lang.getString(R.string.takePhoto), Lang.getString(R.string.pickFromGallery), Lang.getString(R.string.DeletePhoto)}, new int[] {ViewController.OPTION_COLOR_NORMAL, ViewController.OPTION_COLOR_NORMAL, ViewController.OPTION_COLOR_RED}, new int[] {R.drawable.baseline_camera_alt_24, R.drawable.baseline_image_24, R.drawable.baseline_remove_circle_24}); - } else { - context.showOptions(null, new int[] {R.id.btn_changePhotoCamera, R.id.btn_changePhotoGallery}, new String[] {Lang.getString(R.string.takePhoto), Lang.getString(R.string.pickFromGallery)}, null, new int[] {R.drawable.baseline_camera_alt_24, R.drawable.baseline_image_24}); - } - } - - public boolean handlePhotoOption (BaseActivity context, final @IdRes int id, TdApi.User user, EditHeaderView headerView) { - if (user == null && id == R.id.btn_changePhotoDelete && headerView == null) { - return false; - } - if (id == R.id.btn_changePhotoCamera) { - UI.openCameraDelayed(context); - return true; - } else if (id == R.id.btn_changePhotoGallery) { - UI.openGalleryDelayed(context, false); - return true; - } else if (id == R.id.btn_changePhotoDelete) { - if (user != null && user.profilePhoto != null) { - deleteProfilePhoto(user.profilePhoto.id); - } else { - headerView.setPhoto(null); - } - return true; - } - return false; - } - - public void handlePhotoChange (int requestCode, Intent data, EditHeaderView headerView) { - handlePhotoChange(requestCode, data, headerView, headerView == null); - } - - public void handlePhotoChange (int requestCode, Intent data, EditHeaderView headerView, boolean isProfile) { - // TODO show editor - switch (requestCode) { - case Intents.ACTIVITY_RESULT_IMAGE_CAPTURE: { - File image = Intents.takeLastOutputMedia(); - if (image != null) { - U.addToGallery(image); - if (isProfile) { - setProfilePhoto(image.getPath()); - } else { - setEditPhotoCompressed(image.getPath(), headerView); - } - } - break; - } - case Intents.ACTIVITY_RESULT_GALLERY: { - if (data != null) { - final Uri image = data.getData(); - if (image != null) { - String imagePath = U.tryResolveFilePath(image); - if (imagePath != null) { - if (imagePath.endsWith(".webp")) { - UI.showToast("Webp is not supported for profile photos", Toast.LENGTH_LONG); - return; - } - if (isProfile) { - setProfilePhoto(imagePath); - } else { - setEditPhotoCompressed(imagePath, headerView); - } - return; - } - } - } - UI.showToast("Error", Toast.LENGTH_SHORT); - break; - } - } - } - - private static void setEditPhotoCompressed (final String path, final EditHeaderView headerView) { - ImageFile file = new ImageFileLocal(path); - file.setSize(ChatView.getDefaultAvatarCacheSize()); - file.setDecodeSquare(true); - headerView.setPhoto(file); - } - - private void setProfilePhoto (String path) { - UI.showToast(R.string.UploadingPhotoWait, Toast.LENGTH_SHORT); - tdlib.client().send(new TdApi.SetProfilePhoto(new TdApi.InputChatPhotoStatic(new TdApi.InputFileGenerated(path, SimpleGenerationInfo.makeConversion(path), 0)), false), tdlib.profilePhotoHandler()); - } - - private void deleteProfilePhoto (long photoId) { - UI.showToast(R.string.DeletingPhotoWait, Toast.LENGTH_SHORT); - tdlib.client().send(new TdApi.DeleteProfilePhoto(photoId), tdlib.profilePhotoHandler()); - } + // Logs public static void sendTdlibLogs (final ViewController context, final boolean old, final boolean export) { File tdlibLogFile = TdlibManager.getLogFile(old); @@ -1671,8 +1590,6 @@ public static void sendTdlibLogs (final ViewController context, final boolean share.show(); } - // Logs - public static void clearLogs (final boolean old, final RunnableLong after) { Background.instance().post(() -> { try { @@ -7014,4 +6931,271 @@ public void onScrollStateChanged (@NonNull RecyclerView recyclerView, int newSta recyclerView.addOnScrollListener(onScrollListener); return viewMessages; } + + + public static class AvatarPickerManager { + public static final int MODE_PROFILE = 0; + public static final int MODE_PROFILE_PUBLIC = 1; + public static final int MODE_CHAT = 2; + public static final int MODE_NON_CREATED = 3; + + private final ViewController context; + private final Tdlib tdlib; + + public AvatarPickerManager (ViewController context) { + this.context = context; + this.tdlib = context.tdlib(); + } + + public void showMenuForProfile (@Nullable MediaCollectorDelegate delegate, boolean isPublic) { + final ViewController.Options.Builder b = new ViewController.Options.Builder(); + + final TdApi.User user = tdlib.myUser(); + final TdApi.UserFullInfo userFullInfo = tdlib.myUserFull(); + + final long profilePhotoToDelete = isPublic ? + (userFullInfo != null && userFullInfo.publicPhoto != null ? userFullInfo.publicPhoto.id : 0) : + (user != null && user.profilePhoto != null ? user.profilePhoto.id : 0); + + if (profilePhotoToDelete != 0 && !isPublic) { + b.item(new ViewController.OptionItem(R.id.btn_open, Lang.getString(R.string.Open), + ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_visibility_24)); + } + + b.item(new ViewController.OptionItem(R.id.btn_changePhotoGallery, Lang.getString(isPublic ? R.string.SetPublicPhoto : R.string.SetProfilePhoto), + ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_image_24)); + + final Runnable deleteRunnable = () -> showDeletePhotoConfirm(() -> deleteProfilePhoto(profilePhotoToDelete)); + if (profilePhotoToDelete != 0 && !isPublic) { + b.item(new ViewController.OptionItem(R.id.btn_changePhotoDelete, Lang.getString(R.string.Delete), + ViewController.OPTION_COLOR_RED, R.drawable.baseline_delete_24)); + } + + showOptions(b.build(), (itemView, id) -> { + if (id == R.id.btn_open) { + MediaViewController.openFromProfile(context, user, delegate); + } else if (id == R.id.btn_changePhotoGallery) { + openMediaView(false, false, AvatarPickerMode.PROFILE, f -> onProfilePhotoReceived(f, isPublic), + profilePhotoToDelete != 0 ? Lang.getString(isPublic ? R.string.RemovePublicPhoto : R.string.RemoveProfilePhoto) : null, + ColorId.textNegative, deleteRunnable); + } else if (id == R.id.btn_changePhotoDelete) { + deleteRunnable.run(); + } + return true; + }); + } + + public void showMenuForChat (TdApi.Chat chat, MediaCollectorDelegate delegate, boolean allowOpenPhoto) { + if (chat == null) { + return; + } + + final boolean isChannel = tdlib.isChannel(chat.id); + ViewController.Options.Builder b = new ViewController.Options.Builder(); + + if (chat.photo != null && allowOpenPhoto) { + b.item(new ViewController.OptionItem(R.id.btn_open, Lang.getString(R.string.Open), + ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_visibility_24)); + } + + b.item(new ViewController.OptionItem(R.id.btn_changePhotoGallery, Lang.getString(isChannel ? R.string.SetChannelPhoto : R.string.SetGroupPhoto), + ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_image_24)); + + final boolean canDelete = chat.photo != null; + showOptions(b.build(), (itemView, id) -> { + if (id == R.id.btn_open) { + if (chat.photo != null && !TD.isFileEmpty(chat.photo.small)) { + MediaViewController.openFromChat(context, chat, delegate); + } + } else if (id == R.id.btn_changePhotoGallery) { + openMediaView(false, false, isChannel ? AvatarPickerMode.CHANNEL : AvatarPickerMode.GROUP, f -> onChatPhotoReceived(f, chat.id), + canDelete ? Lang.getString(isChannel ? R.string.RemoveChannelPhoto : R.string.RemoveGroupPhoto) : null, ColorId.textNegative, () -> showDeletePhotoConfirm(() -> setChatPhoto(chat.id, null))); + } + return true; + }); + } + + public void showMenuForNonCreatedChat (EditHeaderView headerView, boolean isChannel) { + ViewController.Options.Builder b = new ViewController.Options.Builder(); + + b.item(new ViewController.OptionItem(R.id.btn_changePhotoGallery, Lang.getString(isChannel ? R.string.SetChannelPhoto : R.string.SetGroupPhoto), + ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_image_24)); + + final boolean canDelete = headerView.getImageFile() != null; + showOptions(b.build(), (itemView, id) -> { + if (id == R.id.btn_changePhotoGallery) { + openMediaView(false, false, isChannel ? AvatarPickerMode.CHANNEL : AvatarPickerMode.GROUP, headerView::setPhoto, + canDelete ? Lang.getString(isChannel ? R.string.RemoveChannelPhoto : R.string.RemoveGroupPhoto) : null, ColorId.textNegative, () -> showDeletePhotoConfirm(() -> headerView.setPhoto(null))); + } + return true; + }); + } + + private void showDeletePhotoConfirm (Runnable onConfirm) { + context.showConfirm(Lang.getString(R.string.RemovePhotoConfirm), Lang.getString(R.string.Delete), R.drawable.baseline_delete_24, ViewController.OPTION_COLOR_RED, () -> { + onConfirm.run(); + if (currentMediaLayout != null) { + currentMediaLayout.hide(false); + } + }); + } + + private void showOptions (ViewController.Options options, OptionDelegate delegate) { + if (options.items.length == 1 && options.items[0].id == R.id.btn_changePhotoGallery) { + delegate.onOptionItemPressed(null, R.id.btn_changePhotoGallery); + return; + } + + context.showOptions(options, delegate); + } + + + /* Picker */ + + private MediaLayout currentMediaLayout; + private boolean openingMediaLayout; + + private void openMediaView (boolean ignorePermissionRequest, boolean noMedia, @AvatarPickerMode int avatarPickerMode, RunnableData callback, String customButtonText, @ColorId int customButtonColorId, Runnable customButtonCallback) { + if (openingMediaLayout || currentMediaLayout != null && currentMediaLayout.isVisible()) { + return; + } + + if (!ignorePermissionRequest && context.context().permissions().requestReadExternalStorage(Permissions.ReadType.IMAGES_AND_VIDEOS, grantType -> openMediaView(true, grantType == Permissions.GrantResult.NONE, avatarPickerMode, callback, customButtonText, customButtonColorId, customButtonCallback))) { + return; + } + + final MediaLayout mediaLayout = new MediaLayout(context) { + @Override + public int getCameraButtonOffset () { + return !StringUtils.isEmpty(customButtonText) ? super.getCameraButtonOffset() : 0; + } + }; + mediaLayout.setAvatarPickerMode(avatarPickerMode); + mediaLayout.init(MediaLayout.MODE_AVATAR_PICKER, null); + mediaLayout.setCallback(new MediaLayout.MediaGalleryCallback() { + @Override + public void onSendVideo (ImageGalleryFile file, boolean isFirst) { + if (!isFirst) return; + callback.runWithData(file); + } + + @Override + public void onSendPhoto (ImageGalleryFile file, boolean isFirst) { + if (!isFirst) return; + callback.runWithData(file); + } + }); + if (noMedia) { + mediaLayout.setNoMediaAccess(); + } + + + if (!StringUtils.isEmpty(customButtonText)) { + TextView button = Views.newTextView(context.context(), 16, Theme.getColor(customButtonColorId), Gravity.CENTER, Views.TEXT_FLAG_BOLD | Views.TEXT_FLAG_HORIZONTAL_PADDING); + context.addThemeTextColorListener(button, customButtonColorId); + + button.setText(customButtonText.toUpperCase()); + button.setOnClickListener(v -> customButtonCallback.run()); + + RippleSupport.setSimpleWhiteBackground(button, context); + Views.setClickable(button); + + mediaLayout.setCustomBottomBar(button); + button.setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(55f), Gravity.BOTTOM)); + } + + openingMediaLayout = true; + mediaLayout.preload(() -> { + if (context.isFocused() && !context.isDestroyed()) { + mediaLayout.show(); + } + openingMediaLayout = false; + }, 300L); + + currentMediaLayout = mediaLayout; + } + + + /* Callbacks */ // FIXME: video and webp file checks + + private void onProfilePhotoReceived (ImageGalleryFile file, boolean isPublic) { + Media.instance().post(() -> { + final TdApi.InputFileGenerated inputFile = PhotoGenerationInfo.newFile(file); + UI.post(() -> setProfilePhoto(inputFile, isPublic)); + }); + } + + private void onChatPhotoReceived (ImageGalleryFile file, long chatId) { + Media.instance().post(() -> { + final TdApi.InputFileGenerated inputFile = PhotoGenerationInfo.newFile(file); + UI.post(() -> setChatPhoto(chatId, inputFile)); + }); + } + + + /* Activity Result */ // TODO: show editor + + public boolean handleActivityResult (int requestCode, int resultCode, Intent data, int mode, @Nullable TdApi.Chat chat, @Nullable EditHeaderView headerView) { + if (resultCode != Activity.RESULT_OK) { + return false; + } + + if (requestCode == Intents.ACTIVITY_RESULT_IMAGE_CAPTURE) { + File image = Intents.takeLastOutputMedia(); + if (image != null) { + U.addToGallery(image); + handleActivitySetPhoto(image.getPath(), mode, chat, headerView); + } + return true; + } else if (requestCode == Intents.ACTIVITY_RESULT_GALLERY) { + if (data == null) { + UI.showToast("Error", Toast.LENGTH_SHORT); + return false; + } + + final Uri image = data.getData(); + if (image != null) { + String imagePath = U.tryResolveFilePath(image); + if (imagePath != null) { + if (imagePath.endsWith(".webp")) { + UI.showToast("Webp is not supported for profile photos", Toast.LENGTH_LONG); + return false; + } + handleActivitySetPhoto(imagePath, mode, chat, headerView); + } + } + return true; + } + return false; + } + + private void handleActivitySetPhoto (String path, int mode, @Nullable TdApi.Chat chat, @Nullable EditHeaderView headerView) { + if (mode == MODE_PROFILE || mode == MODE_PROFILE_PUBLIC) { + setProfilePhoto(new TdApi.InputFileGenerated(path, SimpleGenerationInfo.makeConversion(path), 0), mode == MODE_PROFILE_PUBLIC); + } else if (mode == MODE_CHAT && chat != null) { + setChatPhoto(chat.id, new TdApi.InputFileGenerated(path, SimpleGenerationInfo.makeConversion(path), 0)); + } else if (mode == MODE_NON_CREATED && headerView != null) { + U.toGalleryFile(new File(path), false, headerView::setPhoto); + } + } + + + /* Setters */ + + private void setProfilePhoto (TdApi.InputFileGenerated inputFile, boolean isPublic) { + UI.showToast(R.string.UploadingPhotoWait, Toast.LENGTH_SHORT); + tdlib.client().send(new TdApi.SetProfilePhoto(new TdApi.InputChatPhotoStatic(inputFile), isPublic), tdlib.profilePhotoHandler()); + } + + private void deleteProfilePhoto (long profilePhotoId) { + tdlib.client().send(new TdApi.DeleteProfilePhoto(profilePhotoId), tdlib.okHandler()); + } + + private void setChatPhoto (long chatId, @Nullable TdApi.InputFileGenerated inputFile) { + if (inputFile != null) { + UI.showToast(R.string.UploadingPhotoWait, Toast.LENGTH_SHORT); + } + tdlib.client().send(new TdApi.SetChatPhoto(chatId, inputFile != null ? new TdApi.InputChatPhotoStatic(inputFile) : null), tdlib.okHandler()); + } + } } diff --git a/app/src/main/java/org/thunderdog/challegram/tool/DrawAlgorithms.java b/app/src/main/java/org/thunderdog/challegram/tool/DrawAlgorithms.java index dc1a0cf844..bdcb12908c 100644 --- a/app/src/main/java/org/thunderdog/challegram/tool/DrawAlgorithms.java +++ b/app/src/main/java/org/thunderdog/challegram/tool/DrawAlgorithms.java @@ -433,16 +433,17 @@ public static float getCounterWidth (@Dimension(unit = Dimension.DP) float textS } public static void drawCounter (Canvas c, float cx, float cy, int gravity, CounterAnimator counter, float textSize, float textAlpha, TextColorSet colorSet, float scale) { - drawCounter(c, cx, cy, gravity, counter, textSize, textAlpha, false, false, 0, colorSet, null, Gravity.LEFT, 0, 0, 0f, 0f, scale); + drawCounter(c, cx, cy, gravity, counter, textSize, textAlpha, false, false, 0, colorSet, null, Gravity.LEFT, 0, 0, 0f, 0f, scale, null); } - public static void drawCounter (Canvas c, float cx, float cy, int gravity, CounterAnimator counter, float textSize, float textAlpha, boolean needBackground, boolean outlineAffectsBackgroundSize, @Px int backgroundPadding, TextColorSet colorSet, @Nullable Drawable drawable, int drawableGravity, int drawableColorId, @Px int drawableMargin, float backgroundAlpha, float drawableAlpha, float scale) { + public static void drawCounter (Canvas c, float cx, float cy, int gravity, CounterAnimator counter, float textSize, float textAlpha, boolean needBackground, boolean outlineAffectsBackgroundSize, @Px int backgroundPadding, TextColorSet colorSet, @Nullable Drawable drawable, int drawableGravity, int drawableColorId, @Px int drawableMargin, float backgroundAlpha, float drawableAlpha, float scale, @Nullable RectF outputDrawRect) { scale = .6f + .4f * scale; final boolean needScale = scale != 1f; + final float drawRectHeight = Screen.dp(textSize - 2f); final float radius, outlineWidth; if (needBackground) { - radius = Screen.dp(textSize - 2f); + radius = drawRectHeight; outlineWidth = Screen.dp(1.5f); } else { radius = outlineWidth = 0f; @@ -470,6 +471,11 @@ public static void drawCounter (Canvas c, float cx, float cy, int gravity, Count c.scale(scale, scale, rectF.centerX(), rectF.centerY()); } + if (outputDrawRect != null) { + outputDrawRect.set(rectF.left, cy - drawRectHeight, rectF.right, cy + drawRectHeight); + // c.drawRect(outputDrawRect, Paints.strokeSmallPaint(0xFF00FF00)); + } + if (needBackground && backgroundAlpha > 0f) { final int outlineColor = colorSet.outlineColor(false); final int fillingColor = colorSet.backgroundColor(false); @@ -593,10 +599,10 @@ public static void drawScaledBitmap (View view, Canvas c, Bitmap bitmap, int rot final int viewWidth = view.getMeasuredWidth(); final int viewHeight = view.getMeasuredHeight(); - drawScaledBitmap(viewWidth, viewHeight, c, bitmap, rotation, null); + drawScaledBitmap(viewWidth, viewHeight, c, bitmap, rotation, 0f, 0f, null); } - public static void drawScaledBitmap (final int viewWidth, final int viewHeight, Canvas c, Bitmap bitmap, int rotation, @Nullable PaintState paintState) { + public static void drawScaledBitmap (final int viewWidth, final int viewHeight, Canvas c, Bitmap bitmap, int rotation, float mirrorHorizontallyFactor, float mirrorVerticallyFactor, @Nullable PaintState paintState) { if (bitmap != null && !bitmap.isRecycled()) { int bitmapWidth, bitmapHeight; @@ -607,11 +613,14 @@ public static void drawScaledBitmap (final int viewWidth, final int viewHeight, float scaleX = (float) viewHeight / (float) bitmapWidth; float scaleY = (float) viewWidth / (float) bitmapHeight; c.save(); - c.scale(scaleX, scaleY, viewWidth / 2, viewHeight / 2); - c.rotate(rotation, viewWidth / 2, viewHeight / 2); + c.scale(scaleX, scaleY, viewWidth / 2f, viewHeight / 2f); + c.rotate(rotation, viewWidth / 2f, viewHeight / 2f); int x = viewWidth / 2 - bitmapWidth / 2; int y = viewHeight / 2 - bitmapHeight / 2; + c.save(); + c.scale(MathUtils.fromTo(1f, -1f, mirrorHorizontallyFactor), MathUtils.fromTo(1f, -1f, mirrorVerticallyFactor), viewWidth / 2f, viewHeight / 2f); c.drawBitmap(bitmap, x, y, Paints.getBitmapPaint()); + c.restore(); if (paintState != null) { c.clipRect(x, y, x + bitmapWidth, y + bitmapHeight); paintState.draw(c, x, y, bitmapWidth, bitmapHeight); @@ -622,12 +631,15 @@ public static void drawScaledBitmap (final int viewWidth, final int viewHeight, if (saved) { c.save(); if (rotation != 0) { - c.rotate(rotation, viewWidth / 2, viewHeight / 2); + c.rotate(rotation, viewWidth / 2f, viewHeight / 2f); } } Rect dst = Paints.getRect(); dst.set(0, 0, viewWidth, viewHeight); + c.save(); + c.scale(MathUtils.fromTo(1f, -1f, mirrorHorizontallyFactor), MathUtils.fromTo(1f, -1f, mirrorVerticallyFactor), viewWidth / 2f, viewHeight / 2f); c.drawBitmap(bitmap, null, dst, Paints.getBitmapPaint()); + c.restore(); if (paintState != null && !paintState.isEmpty()) { c.clipRect(0, 0, viewWidth, viewHeight); paintState.draw(c, 0, 0, viewWidth, viewHeight); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/CreateChannelController.java b/app/src/main/java/org/thunderdog/challegram/ui/CreateChannelController.java index 6ab1fc5948..b7d6d6e645 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/CreateChannelController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/CreateChannelController.java @@ -14,7 +14,6 @@ */ package org.thunderdog.challegram.ui; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -34,14 +33,15 @@ import org.thunderdog.challegram.R; import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.emoji.EmojiFilter; -import org.thunderdog.challegram.filegen.SimpleGenerationInfo; -import org.thunderdog.challegram.loader.ImageFile; +import org.thunderdog.challegram.filegen.PhotoGenerationInfo; +import org.thunderdog.challegram.loader.ImageGalleryFile; import org.thunderdog.challegram.navigation.ActivityResultHandler; import org.thunderdog.challegram.navigation.BackHeaderButton; import org.thunderdog.challegram.navigation.EditHeaderView; import org.thunderdog.challegram.navigation.ViewController; import org.thunderdog.challegram.support.ViewSupport; import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.telegram.TdlibUi; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.Theme; import org.thunderdog.challegram.tool.Fonts; @@ -51,7 +51,6 @@ import org.thunderdog.challegram.tool.Views; import org.thunderdog.challegram.unsorted.Size; import org.thunderdog.challegram.util.CharacterStyleFilter; -import org.thunderdog.challegram.util.OptionDelegate; import org.thunderdog.challegram.v.EditText; import org.thunderdog.challegram.widget.EmojiEditText; import org.thunderdog.challegram.widget.NoScrollTextView; @@ -61,9 +60,12 @@ import me.vkryl.core.StringUtils; import me.vkryl.td.TdConstants; -public class CreateChannelController extends ViewController implements EditHeaderView.ReadyCallback, OptionDelegate, ActivityResultHandler, TextView.OnEditorActionListener { +public class CreateChannelController extends ViewController implements EditHeaderView.ReadyCallback, ActivityResultHandler, TextView.OnEditorActionListener { + private final TdlibUi.AvatarPickerManager avatarPickerManager; + public CreateChannelController (Context context, Tdlib tdlib) { super(context, tdlib); + avatarPickerManager = new TdlibUi.AvatarPickerManager(this); } private EditText descView; @@ -144,6 +146,9 @@ protected View onCreateView (Context context) { headerCell = new EditHeaderView(context, this); headerCell.setInputOptions(R.string.ChannelName, InputType.TYPE_TEXT_FLAG_CAP_WORDS); + headerCell.setOnPhotoClickListener(() -> { + avatarPickerManager.showMenuForNonCreatedChat(headerCell, true); + }); headerCell.setNextField(R.id.edit_description); headerCell.setReadyCallback(this); setLockFocusView(headerCell.getInputView()); @@ -217,17 +222,9 @@ protected void onFloatingButtonPressed () { createChannel(); } - @Override - public boolean onOptionItemPressed (View optionItemView, int id) { - tdlib.ui().handlePhotoOption(context, id, null, headerCell); - return true; - } - @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK) { - tdlib.ui().handlePhotoChange(requestCode, data, headerCell); - } + avatarPickerManager.handleActivityResult(requestCode, resultCode, data, TdlibUi.AvatarPickerManager.MODE_NON_CREATED, null, headerCell); } @Override @@ -251,14 +248,6 @@ public String getDescription () { return descView.getText().toString(); } - public String getPhoto () { - return headerCell.getPhoto(); - } - - public ImageFile getImageFile () { - return headerCell.getImageFile(); - } - public void setDescription (String description) { if (description != null) { descView.setText(description); @@ -267,8 +256,7 @@ public void setDescription (String description) { } private boolean isCreating; - private String currentPhoto; - private ImageFile currentImageFile; + private ImageGalleryFile currentImageFile; private void toggleCreating () { isCreating = !isCreating; @@ -288,10 +276,9 @@ public void createChannel () { toggleCreating(); - currentPhoto = getPhoto(); - currentImageFile = getImageFile(); + currentImageFile = headerCell.getImageFile(); - UI.showProgress(Lang.getString(R.string.ProgressCreateChannel), null, 300l); + UI.showProgress(Lang.getString(R.string.ProgressCreateChannel), null, 300L); tdlib.send(new TdApi.CreateNewSupergroupChat(title, false, true, desc, null, 0, false), (remoteChat, error) -> { UI.hideProgress(); @@ -301,8 +288,8 @@ public void createChannel () { } else { long chatId = remoteChat.id; chat = tdlib.chatStrict(chatId); - if (currentPhoto != null) { - tdlib.client().send(new TdApi.SetChatPhoto(chat.id, new TdApi.InputChatPhotoStatic(new TdApi.InputFileGenerated(currentPhoto, SimpleGenerationInfo.makeConversion(currentPhoto), 0))), tdlib.okHandler()); + if (currentImageFile != null) { + tdlib.client().send(new TdApi.SetChatPhoto(chat.id, new TdApi.InputChatPhotoStatic(PhotoGenerationInfo.newFile(currentImageFile))), tdlib.okHandler()); } } UI.post(() -> channelCreated(chat)); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/CreateGroupController.java b/app/src/main/java/org/thunderdog/challegram/ui/CreateGroupController.java index 7ad4328739..26a814bf7d 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/CreateGroupController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/CreateGroupController.java @@ -14,7 +14,6 @@ */ package org.thunderdog.challegram.ui; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -35,7 +34,8 @@ import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.data.TD; import org.thunderdog.challegram.data.TGUser; -import org.thunderdog.challegram.filegen.SimpleGenerationInfo; +import org.thunderdog.challegram.filegen.PhotoGenerationInfo; +import org.thunderdog.challegram.loader.ImageGalleryFile; import org.thunderdog.challegram.navigation.ActivityResultHandler; import org.thunderdog.challegram.navigation.BackHeaderButton; import org.thunderdog.challegram.navigation.EditHeaderView; @@ -44,13 +44,13 @@ import org.thunderdog.challegram.support.ViewSupport; import org.thunderdog.challegram.telegram.Tdlib; import org.thunderdog.challegram.telegram.TdlibCache; +import org.thunderdog.challegram.telegram.TdlibUi; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.tool.Keyboard; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.UI; import org.thunderdog.challegram.tool.Views; import org.thunderdog.challegram.unsorted.Size; -import org.thunderdog.challegram.util.OptionDelegate; import org.thunderdog.challegram.util.Unlockable; import org.thunderdog.challegram.widget.ListInfoView; @@ -59,12 +59,15 @@ import me.vkryl.android.widget.FrameLayoutFix; import me.vkryl.core.ArrayUtils; -public class CreateGroupController extends ViewController implements EditHeaderView.ReadyCallback, OptionDelegate, Client.ResultHandler, Unlockable, ActivityResultHandler, +public class CreateGroupController extends ViewController implements EditHeaderView.ReadyCallback, Client.ResultHandler, Unlockable, ActivityResultHandler, TdlibCache.UserDataChangeListener, TdlibCache.UserStatusChangeListener { + + private final TdlibUi.AvatarPickerManager avatarPickerManager; private ArrayList members; public CreateGroupController (Context context, Tdlib tdlib) { super(context, tdlib); + avatarPickerManager = new TdlibUi.AvatarPickerManager(this); } public void setMembers (ArrayList members) { @@ -79,6 +82,9 @@ public void setMembers (ArrayList members) { protected View onCreateView (Context context) { headerCell = new EditHeaderView(context, this); headerCell.setInputOptions(R.string.GroupName, InputType.TYPE_TEXT_FLAG_CAP_WORDS); + headerCell.setOnPhotoClickListener(() -> { + avatarPickerManager.showMenuForNonCreatedChat(headerCell, false); + }); headerCell.setImeOptions(EditorInfo.IME_ACTION_DONE); headerCell.setReadyCallback(this); setLockFocusView(headerCell.getInputView()); @@ -334,10 +340,9 @@ public void detachReceiver () { private void onUserClick (TGUser user) { pickedUser = user; - showOptions(null, new int[] {R.id.btn_deleteMember, R.id.btn_cancel}, new String[] {Lang.getString(R.string.GroupDontAdd), Lang.getString(R.string.Cancel)}, new int[] {OPTION_COLOR_RED, OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_remove_circle_24, R.drawable.baseline_cancel_24}); + showOptions(null, new int[] {R.id.btn_deleteMember, R.id.btn_cancel}, new String[] {Lang.getString(R.string.GroupDontAdd), Lang.getString(R.string.Cancel)}, new int[] {OPTION_COLOR_RED, OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_remove_circle_24, R.drawable.baseline_cancel_24}, this::onOptionItemPressed); } - @Override public boolean onOptionItemPressed (View optionItemView, int id) { if (id == R.id.btn_deleteMember) { if (pickedUser != null) { @@ -356,17 +361,13 @@ public boolean onOptionItemPressed (View optionItemView, int id) { } } } - } else { - tdlib.ui().handlePhotoOption(context, id, null, headerCell); } return true; } @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK) { - tdlib.ui().handlePhotoChange(requestCode, data, headerCell); - } + avatarPickerManager.handleActivityResult(requestCode, resultCode, data, TdlibUi.AvatarPickerManager.MODE_NON_CREATED, null, headerCell); } @Override @@ -382,7 +383,7 @@ protected void onFloatingButtonPressed () { } private boolean isCreating; - private String currentPhoto; + private ImageGalleryFile currentImageFile; private long[] currentMemberIds; private boolean currentIsChannel; @@ -409,7 +410,7 @@ public void createGroup () { headerCell.setInputEnabled(false); isCreating = true; - currentPhoto = headerCell.getPhoto(); + currentImageFile = headerCell.getImageFile(); String title = headerCell.getInput(); @@ -462,8 +463,8 @@ public void onResult (TdApi.Object object) { if (currentIsChannel) { tdlib.client().send(new TdApi.AddChatMembers(chatId, currentMemberIds), this); } - if (currentPhoto != null) { - tdlib.client().send(new TdApi.SetChatPhoto(chatId, new TdApi.InputChatPhotoStatic(new TdApi.InputFileGenerated(currentPhoto, SimpleGenerationInfo.makeConversion(currentPhoto), 0))), this); + if (currentImageFile != null) { + tdlib.client().send(new TdApi.SetChatPhoto(chatId, new TdApi.InputChatPhotoStatic(PhotoGenerationInfo.newFile(currentImageFile))), this); } tdlib.ui().post(() -> { if (groupCreationCallback == null || !groupCreationCallback.onGroupCreated(this, (TdApi.Chat) object)) { diff --git a/app/src/main/java/org/thunderdog/challegram/ui/CreatePollController.java b/app/src/main/java/org/thunderdog/challegram/ui/CreatePollController.java index d24e1b19cf..bff3bc5cbf 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/CreatePollController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/CreatePollController.java @@ -650,6 +650,12 @@ private void send (TdApi.MessageSendOptions sendOptions, boolean disableMarkdown return; } + final CharSequence slowModeRestrictionText = tdlib.getSlowModeRestrictionText(chatId, sendOptions.schedulingState); + if (slowModeRestrictionText != null) { + context().tooltipManager().builder(getDoneButton()).controller(this).show(tdlib, slowModeRestrictionText).hideDelayed(); + return; + } + TdApi.FormattedText explanation = getExplanation(!disableMarkdown); // TODO lock texts diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditRightsController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditRightsController.java index d9626d0e62..d0fb7c5365 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EditRightsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditRightsController.java @@ -21,6 +21,7 @@ import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -259,6 +260,11 @@ public void onClick (View view) { .show(this, tdlib, R.drawable.baseline_info_24, text); } } + } else if (viewId == R.id.btn_togglePermissionGroup) { + RightOption option = (RightOption) item.getData(); + if (option != null && option.groupRightIds != null) { + toggleRightsGroupVisibility(option.groupRightIds); + } } else if (viewId == R.id.btn_transferOwnership) { if (ChatId.isBasicGroup(getArgumentsStrict().chatId)) { showConfirm(Lang.getMarkdownString(this, R.string.UpgradeChatPrompt), Lang.getString(R.string.Proceed), this::onTransferOwnershipClick); @@ -317,6 +323,9 @@ public void onClick (View view) { targetAdmin.rights.canManageVideoChats = false; targetAdmin.rights.isAnonymous = false; targetAdmin.rights.canPromoteMembers = false; + targetAdmin.rights.canPostStories = false; + targetAdmin.rights.canEditStories = false; + targetAdmin.rights.canDeleteStories = false; updateValues(); setDoneInProgress(true); setDoneVisible(true); @@ -481,21 +490,39 @@ protected void modifyEditText (ListItem item, ViewGroup parent, MaterialEditText @SuppressWarnings("WrongConstant") protected void setValuedSetting (ListItem item, SettingView view, boolean isUpdate) { final int viewId = item.getId(); + boolean needRotateIcon = false; if (viewId == R.id.btn_togglePermission) { @RightId int rightId = item.getIntValue(); boolean canEdit = hasAccessToEditRight(item); view.setIgnoreEnabled(true); view.setEnabled(canEdit || getHintForToggleUnavailability(item) != null); view.setVisuallyEnabled(canEdit, isUpdate); + boolean isToggler = item.getViewType() == ListItem.TYPE_VALUED_SETTING_COMPACT_WITH_TOGGLER; + boolean needAddData = getArgumentsStrict().mode == MODE_CHAT_PERMISSIONS; + if (isToggler) { view.getToggler().setUseNegativeState(true); } view.getToggler().setRadioEnabled(item.getBoolValue(), isUpdate); view.getToggler().setShowLock(!canEdit); - if (isToggler) { + if (needAddData) { view.setData(item.getBoolValue() ? R.string.AllMembers : (rightId == RightId.INVITE_USERS || rightId == RightId.CHANGE_CHAT_INFO || rightId == RightId.PIN_MESSAGES) ? R.string.OnlyAdminsSpecific : R.string.OnlyAdmins); } + } else if (viewId == R.id.btn_togglePermissionGroup) { + final RightOption option = (RightOption) item.getData(); + if (option != null && option.groupRightIds != null) { + final boolean canEdit = hasAccessToEditRightsGroup(option.groupRightIds); + final int count = getRightsGroupEnabledCount(option.groupRightIds); + view.setIgnoreEnabled(true); + view.setEnabled(true /*canEdit || getHintForToggleUnavailability(item) != null*/); + view.setVisuallyEnabled(canEdit, isUpdate); + view.getToggler().setUseNegativeState(true); + view.getToggler().setRadioEnabled(item.getBoolValue(), isUpdate); + view.getToggler().setShowLock(!canEdit); + view.setData(Lang.pluralBold(R.string.xPermissionsSendMediaAllowed, count, option.groupRightIds.length)); + needRotateIcon = adapter.indexOfViewByIdAndValue(R.id.btn_togglePermission, option.groupRightIds[0]) != -1; + } } else if (viewId == R.id.btn_date) { boolean canEdit = hasAccessToEditRight(item); view.setIgnoreEnabled(true); @@ -521,12 +548,40 @@ protected void setValuedSetting (ListItem item, SettingView view, boolean isUpda view.setName(canViewMessages ? R.string.RestrictFor : R.string.BlockFor); } } + if (view.getToggler() != null) { + if (viewId == R.id.btn_togglePermissionGroup) { + view.getToggler().setOnClickListener(v -> { + ListItem i = (ListItem) view.getTag(); + if (i.getId() == R.id.btn_togglePermissionGroup) { + final RightOption option = (RightOption) item.getData(); + if (option != null && option.groupRightIds != null) { + toggleRightsGroup(option.groupRightIds); + } + } else { + view.performClick(); + } + }); + view.getToggler().setClickable(true); + } else { + view.getToggler().setOnClickListener(null); + view.getToggler().setClickable(false); + } + } + view.setIconRotated(needRotateIcon, isUpdate); } @Override protected void setChatData (ListItem item, int position, BetterChatView chatView) { chatView.setChat((TGFoundChat) item.getData()); } + + @Override + public void modifySettingView (int viewType, SettingView settingView) { + super.modifySettingView(viewType, settingView); + if (viewType == ListItem.TYPE_VALUED_SETTING_COMPACT_WITH_TOGGLER || viewType == ListItem.TYPE_RADIO_SETTING_WITH_NEGATIVE_STATE) { + settingView.forcePadding(Screen.dp(73), 0); + } + } }; buildCells(); @@ -690,6 +745,11 @@ private boolean hasAccessToEditRight (ListItem item) { throw new UnsupportedOperationException(); } + return hasAccessToEditRight(id); + } + + private boolean hasAccessToEditRight (int id) { + Args args = getArgumentsStrict(); if (args.mode == MODE_CHAT_PERMISSIONS) { if (tdlib.canRestrictMembers(args.chatId)) { TdApi.Chat chat = tdlib.chatStrict(args.chatId); @@ -994,113 +1054,74 @@ private void buildCells () { args.mode == MODE_RESTRICTION ? args.senderId.getConstructor() == TdApi.MessageSenderChat.CONSTRUCTOR ? (tdlib.isChannel(((TdApi.MessageSenderChat) args.senderId).chatId) ? R.string.WhatThisChannelCanDo : R.string.WhatThisGroupCanDo) : R.string.WhatThisUserCanDo : R.string.WhatThisAdminCanDo)); items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); - boolean isChannel = tdlib.isChannel(args.chatId); - - @RightId int[] rightIds; + final boolean isChannel = tdlib.isChannel(args.chatId); + final ArrayList rightIdOptions = new ArrayList<>(12); if (args.mode == MODE_CHAT_PERMISSIONS) { - rightIds = new int[] { - RightId.SEND_BASIC_MESSAGES, - RightId.SEND_PHOTOS, - RightId.SEND_VIDEOS, - RightId.SEND_AUDIO, - RightId.SEND_DOCS, - RightId.SEND_VOICE_NOTES, - RightId.SEND_VIDEO_NOTES, - RightId.SEND_OTHER_MESSAGES, - RightId.SEND_POLLS, - RightId.EMBED_LINKS, - RightId.INVITE_USERS, - RightId.PIN_MESSAGES, - RightId.CHANGE_CHAT_INFO - }; + rightIdOptions.add(new RightOption(RightId.SEND_BASIC_MESSAGES)); + rightIdOptions.add(new RightOption(R.string.RightSendMedia, SEND_MEDIA_RIGHT_IDS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.PIN_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); } else if (args.mode == MODE_RESTRICTION) { if (args.senderId.getConstructor() == TdApi.MessageSenderChat.CONSTRUCTOR) { - rightIds = new int[] { - RightId.SEND_BASIC_MESSAGES, - RightId.SEND_PHOTOS, - RightId.SEND_VIDEOS, - RightId.SEND_AUDIO, - RightId.SEND_DOCS, - RightId.SEND_VOICE_NOTES, - RightId.SEND_VIDEO_NOTES, - RightId.SEND_OTHER_MESSAGES, - RightId.SEND_POLLS, - RightId.EMBED_LINKS, - RightId.INVITE_USERS, - RightId.PIN_MESSAGES, - RightId.CHANGE_CHAT_INFO - }; + rightIdOptions.add(new RightOption(RightId.SEND_BASIC_MESSAGES)); + rightIdOptions.add(new RightOption(R.string.RightSendMedia, SEND_MEDIA_RIGHT_IDS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.PIN_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); } else { - rightIds = new int[] { - RightId.READ_MESSAGES, - RightId.SEND_BASIC_MESSAGES, - RightId.SEND_PHOTOS, - RightId.SEND_VIDEOS, - RightId.SEND_AUDIO, - RightId.SEND_DOCS, - RightId.SEND_VOICE_NOTES, - RightId.SEND_VIDEO_NOTES, - RightId.SEND_OTHER_MESSAGES, - RightId.SEND_POLLS, - RightId.EMBED_LINKS, - RightId.INVITE_USERS, - RightId.PIN_MESSAGES, - RightId.CHANGE_CHAT_INFO - }; + rightIdOptions.add(new RightOption(RightId.READ_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.SEND_BASIC_MESSAGES)); + rightIdOptions.add(new RightOption(R.string.RightSendMedia, SEND_MEDIA_RIGHT_IDS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.PIN_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); } } else if (isChannel) { - rightIds = new int[] { - RightId.CHANGE_CHAT_INFO, - RightId.SEND_BASIC_MESSAGES, - RightId.EDIT_MESSAGES, - RightId.DELETE_MESSAGES, - RightId.INVITE_USERS, - RightId.MANAGE_VIDEO_CHATS, - RightId.POST_STORIES, - RightId.EDIT_STORIES, - RightId.DELETE_STORIES, - RightId.ADD_NEW_ADMINS - }; + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); + rightIdOptions.add(new RightOption(R.string.RightMessages, MANAGE_CHANNEL_POSTS_IDS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.MANAGE_VIDEO_CHATS)); + rightIdOptions.add(new RightOption(RightId.ADD_NEW_ADMINS)); + rightIdOptions.add(new RightOption(R.string.RightStories, MANAGE_STORIES_RIGHT_IDS)); } else if (isForum) { - rightIds = new int[] { - RightId.CHANGE_CHAT_INFO, - RightId.DELETE_MESSAGES, - RightId.BAN_USERS, - RightId.INVITE_USERS, - RightId.PIN_MESSAGES, - RightId.MANAGE_VIDEO_CHATS, - RightId.MANAGE_TOPICS, - RightId.REMAIN_ANONYMOUS, - RightId.ADD_NEW_ADMINS - }; + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); + rightIdOptions.add(new RightOption( RightId.DELETE_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.BAN_USERS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.PIN_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.MANAGE_VIDEO_CHATS)); + rightIdOptions.add(new RightOption(RightId.MANAGE_TOPICS)); + rightIdOptions.add(new RightOption(RightId.REMAIN_ANONYMOUS)); + rightIdOptions.add(new RightOption(RightId.ADD_NEW_ADMINS)); } else { - rightIds = new int[] { - RightId.CHANGE_CHAT_INFO, - RightId.DELETE_MESSAGES, - RightId.BAN_USERS, - RightId.INVITE_USERS, - RightId.PIN_MESSAGES, - RightId.MANAGE_VIDEO_CHATS, - RightId.REMAIN_ANONYMOUS, - RightId.ADD_NEW_ADMINS - }; + rightIdOptions.add(new RightOption(RightId.CHANGE_CHAT_INFO)); + rightIdOptions.add(new RightOption(RightId.DELETE_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.BAN_USERS)); + rightIdOptions.add(new RightOption(RightId.INVITE_USERS)); + rightIdOptions.add(new RightOption(RightId.PIN_MESSAGES)); + rightIdOptions.add(new RightOption(RightId.MANAGE_VIDEO_CHATS)); + rightIdOptions.add(new RightOption(RightId.REMAIN_ANONYMOUS)); + rightIdOptions.add(new RightOption(RightId.ADD_NEW_ADMINS)); } boolean first = true; int viewType = args.mode == MODE_CHAT_PERMISSIONS ? ListItem.TYPE_VALUED_SETTING_COMPACT_WITH_TOGGLER : ListItem.TYPE_RADIO_SETTING_WITH_NEGATIVE_STATE; - for (@RightId int rightId : rightIds) { + for (RightOption option: rightIdOptions) { if (first) { first = false; } else { items.add(new ListItem(ListItem.TYPE_SEPARATOR_FULL)); } - items.add(new ListItem( - viewType, - R.id.btn_togglePermission, 0, - stringForRightId(rightId, isChannel) - ).setIntValue(rightId) - .setBoolValue(getValueForId(rightId)) - ); + if (option.groupRightIds == null) { + final @RightId int rightId = option.singleRightId; + items.add(new ListItem(viewType, R.id.btn_togglePermission, iconForRightId(rightId), stringForRightId(rightId, isChannel)) + .setIntValue(rightId).setBoolValue(getValueForId(rightId))); + } else { + items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT_WITH_TOGGLER, + R.id.btn_togglePermissionGroup, R.drawable.round_keyboard_arrow_right_24, option.name + ).setIntValue(option.groupRightIds[0]).setData(option).setBoolValue(getRightsGroupEnabledCount(option.groupRightIds) > 0)); + } } if (args.mode == MODE_RESTRICTION) { @@ -1346,6 +1367,14 @@ private void setCanViewMessages (boolean value) { private void toggleValueForRightId (@RightId int id) { final boolean newValue = !getValueForId(id); + setValueForRightId(id, newValue); + } + + private void setValueForRightId (@RightId int id, boolean newValue) { + if (getValueForId(id) == newValue) { + return; + } + switch (id) { case RightId.READ_MESSAGES: { setCanViewMessages(newValue); @@ -1504,9 +1533,19 @@ private void updateValues () { item.setBoolValue(value); adapter.updateValuedSettingByPosition(i); } + } else if (item.getId() == R.id.btn_togglePermissionGroup) { + final RightOption option = (RightOption) item.getData(); + if (option != null && option.groupRightIds != null) { + final boolean value = getRightsGroupEnabledCount(option.groupRightIds) > 0; + if (value != item.getBoolValue()) { + item.setBoolValue(value); + adapter.updateValuedSettingByPosition(i); + } + } } i++; } + adapter.updateAllValuedSettingsById(R.id.btn_togglePermissionGroup); } private boolean checkDefaultRight (@RightId int id) { @@ -1645,6 +1684,52 @@ private boolean getValueForId (@RightId int id) { throw new UnsupportedOperationException(Lang.getResourceEntryName(id)); } + private @DrawableRes int iconForRightId (@RightId int id) { + switch (id) { + case RightId.READ_MESSAGES: + return R.drawable.baseline_eye_off_24; + case RightId.SEND_BASIC_MESSAGES: + return R.drawable.baseline_format_text_24; + case RightId.CHANGE_CHAT_INFO: + case RightId.EDIT_MESSAGES: + case RightId.EDIT_STORIES: + return R.drawable.baseline_edit_24; + case RightId.DELETE_STORIES: + case RightId.DELETE_MESSAGES: + return R.drawable.baseline_delete_24; + case RightId.BAN_USERS: + return R.drawable.baseline_block_24; + case RightId.INVITE_USERS: + return R.drawable.baseline_person_add_24; + case RightId.ADD_NEW_ADMINS: + return R.drawable.baseline_stars_24; + case RightId.PIN_MESSAGES: + return R.drawable.deproko_baseline_pin_24; + case RightId.REMAIN_ANONYMOUS: + return R.drawable.dot_baseline_acc_anon_24; + + case RightId.MANAGE_VIDEO_CHATS: + return R.drawable.baseline_video_chat_24; + case RightId.MANAGE_TOPICS: + return R.drawable.baseline_format_list_bulleted_type_24; + + case RightId.POST_STORIES: // todo + + + case RightId.SEND_AUDIO: + case RightId.SEND_DOCS: + case RightId.SEND_PHOTOS: + case RightId.SEND_VIDEOS: + case RightId.SEND_VOICE_NOTES: + case RightId.SEND_VIDEO_NOTES: + case RightId.SEND_OTHER_MESSAGES: + case RightId.SEND_POLLS: + case RightId.EMBED_LINKS: + default: + return 0; + } + } + private boolean canViewOrEditCustomTitle () { Args args = getArgumentsStrict(); if (tdlib.isChannel(args.chatId)) @@ -1689,4 +1774,109 @@ public CharSequence getName () { throw new AssertionError(); } + private boolean hasAccessToEditRightsGroup (int[] rights) { + for (int right : rights) { + if (hasAccessToEditRight(right)) { + return true; + } + } + return false; + } + + private void toggleRightsGroup (int[] rights) { + final int index = adapter.indexOfViewByIdAndValue(R.id.btn_togglePermissionGroup, rights[0]); + final ListItem item = adapter.getItem(index); + if (item == null) { + return; + } + + boolean newValue = !item.getBoolValue(); + for (int rightId : rights) { + boolean canEdit = hasAccessToEditRight(rightId); + if (canEdit) { + setValueForRightId(rightId, newValue); + } + } + } + + private int getRightsGroupEnabledCount (int[] rights) { + int res = 0; + for (int right : rights) { + res += getValueForId(right) ? 1 : 0; + } + + return res; + } + + private void toggleRightsGroupVisibility (int[] rights) { + final int index = adapter.indexOfViewByIdAndValue(R.id.btn_togglePermissionGroup, rights[0]); + if (index == -1) return; + + final boolean sendMediaGroupIsVisible = indexOfViewByRightId(rights[0]) != -1; + + if (!sendMediaGroupIsVisible) { + final Args args = getArgumentsStrict(); + final boolean isChannel = tdlib.isChannel(args.chatId); + final int viewType = args.mode == MODE_CHAT_PERMISSIONS ? + ListItem.TYPE_VALUED_SETTING_COMPACT_WITH_TOGGLER : + ListItem.TYPE_RADIO_SETTING_WITH_NEGATIVE_STATE; + + ArrayList items = new ArrayList<>(rights.length * 2); + for (@RightId int rightId : rights) { + items.add(new ListItem(ListItem.TYPE_SEPARATOR)); + items.add(new ListItem(viewType, + R.id.btn_togglePermission, 0, + stringForRightId(rightId, isChannel) + ).setIntValue(rightId) + .setBoolValue(getValueForId(rightId)) + ); + } + adapter.addItems(index + 1, items.toArray(new ListItem[0])); + } else { + adapter.removeRange(index + 1, rights.length * 2); + } + adapter.updateValuedSettingByPosition(index); + } + + public static final int[] SEND_MEDIA_RIGHT_IDS = { + RightId.SEND_PHOTOS, + RightId.SEND_VIDEOS, + RightId.SEND_AUDIO, + RightId.SEND_DOCS, + RightId.SEND_VOICE_NOTES, + RightId.SEND_VIDEO_NOTES, + RightId.SEND_OTHER_MESSAGES, + RightId.SEND_POLLS, + RightId.EMBED_LINKS, + }; + + private static final int[] MANAGE_CHANNEL_POSTS_IDS = { + RightId.SEND_BASIC_MESSAGES, + RightId.EDIT_MESSAGES, + RightId.DELETE_MESSAGES + }; + + private static final int[] MANAGE_STORIES_RIGHT_IDS = { + RightId.POST_STORIES, + RightId.EDIT_STORIES, + RightId.DELETE_STORIES + }; + + private static class RightOption { + public final @RightId int singleRightId; + public final @Nullable @RightId int[] groupRightIds; + public final @StringRes int name; + + public RightOption (@RightId int singleRightId) { + this.singleRightId = singleRightId; + this.groupRightIds = null; + this.name = -1; + } + + public RightOption (@StringRes int name, @NonNull @RightId int[] groupRightIds) { + this.groupRightIds = groupRightIds; + this.singleRightId = groupRightIds[0]; + this.name = name; + } + } } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusListController.java b/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusListController.java index 95085a925d..cf3fe88f43 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusListController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusListController.java @@ -1406,9 +1406,9 @@ public void onMenuStickerPreviewClick (View v, ViewController context, @NonNu futureRes = R.string.SetDateAt; context.showDateTimePicker(tdlib, Lang.getString(titleRes), todayRes, tomorrowRes, futureRes, millis -> { - int duration = (int) ((millis - System.currentTimeMillis()) / 1000L); - stickerSmallView.onSetEmojiStatus(v, sticker, emojiId, duration); - tdlib.client().send(new TdApi.SetEmojiStatus(new TdApi.EmojiStatus(emojiId, duration)), tdlib.okHandler()); + long expirationDate = millis / 1000L; + stickerSmallView.onSetEmojiStatus(v, sticker, emojiId, expirationDate); + tdlib.client().send(new TdApi.SetEmojiStatus(new TdApi.EmojiStatus(emojiId, (int) expirationDate)), tdlib.okHandler()); stickerSmallView.closePreviewIfNeeded(); }, null); return true; @@ -1426,8 +1426,9 @@ public void onMenuStickerPreviewClick (View v, ViewController context, @NonNu } else { duration = 0; } - stickerSmallView.onSetEmojiStatus(v, sticker, emojiId, duration); - tdlib.client().send(new TdApi.SetEmojiStatus(new TdApi.EmojiStatus(emojiId, duration)), tdlib.okHandler()); + long expirationDate = System.currentTimeMillis() / 1000L + duration; + stickerSmallView.onSetEmojiStatus(v, sticker, emojiId, expirationDate); + tdlib.client().send(new TdApi.SetEmojiStatus(new TdApi.EmojiStatus(emojiId, (int) expirationDate)), tdlib.okHandler()); stickerSmallView.closePreviewIfNeeded(); return true; }); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusSelectorEmojiPage.java b/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusSelectorEmojiPage.java index 84e0174c1c..c1680103be 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusSelectorEmojiPage.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EmojiStatusSelectorEmojiPage.java @@ -120,7 +120,7 @@ public boolean onTouchEvent (MotionEvent e) { emojiCustomListController = new EmojiStatusListController(context, tdlib) { @Override - public void onSetEmojiStatusFromPreview (StickerSmallView view, View clickView, TGStickerObj sticker, long emojiId, int duration) { + public void onSetEmojiStatusFromPreview (StickerSmallView view, View clickView, TGStickerObj sticker, long emojiId, long expirationDate) { context.replaceReactionPreviewCords(parent.animationDelegate.getDestX(), parent.animationDelegate.getDestY()); parent.hidePopupWindow(true); scheduleClickAnimation(sticker.getCustomEmojiId()); 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 f34970c012..33170ed8ac 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/MessagesController.java @@ -266,6 +266,7 @@ import me.vkryl.android.AnimatorUtils; import me.vkryl.android.animator.BoolAnimator; import me.vkryl.android.animator.FactorAnimator; +import me.vkryl.android.util.ClickHelper; import me.vkryl.android.widget.FrameLayoutFix; import me.vkryl.core.ArrayUtils; import me.vkryl.core.BitwiseUtils; @@ -330,6 +331,17 @@ public void setDestroyInstance () { private BotHelper botHelper; private @Nullable InputView inputView; + private final ClickHelper inputViewDisabledClickHelper = new ClickHelper(new ClickHelper.Delegate() { + @Override + public boolean needClickAt (View view, float x, float y) { + return !hasSendBasicMessagePermission(); + } + + @Override + public void onClickAt (View view, float x, float y) { + context().tooltipManager().builder(view).show(tdlib, R.string.MessageInputTextDisabledHint).hideDelayed(); + } + }); private SeparatorView bottomShadowView; private boolean enableOnResume; @@ -698,6 +710,7 @@ protected void onLayout (boolean changed, int left, int top, int right, int bott @Override public boolean onTouchEvent (MotionEvent event) { boolean r = super.onTouchEvent(event); + inputViewDisabledClickHelper.onTouchEvent(this, event); if (textFormattingLayout != null) { textFormattingLayout.onInputViewTouchEvent(event); } @@ -1165,6 +1178,7 @@ public void onClick () { } sendButton = new SendButton(context, areScheduled ? R.drawable.baseline_schedule_24 : R.drawable.deproko_baseline_send_24); + sendButton.setIgnoreDrawMessageSender(); sendButton.setOnClickListener(this); addThemeInvalidateListener(sendButton); sendButton.setId(R.id.msg_send); @@ -2585,16 +2599,18 @@ private void shareItem () { } public void shareItem (Object item) { - if (!hasWritePermission()) { // FIXME right - return; - } - if (item instanceof InlineResultButton) { + if (!hasSendBasicMessagePermission()) { + return; + } processSwitchPm((InlineResultButton) item); return; } if (item instanceof TGSwitchInline) { + if (!hasSendBasicMessagePermission()) { + return; + } if (inputView != null) { inputView.setInput(item.toString(), true, false); } @@ -2612,6 +2628,9 @@ public void shareItem (Object item) { } if (item instanceof TGRecord) { + if (!hasSendMessagePermission(RightId.SEND_VOICE_NOTES)) { + return; + } processRecord((TGRecord) item); return; } @@ -2663,6 +2682,13 @@ private void updateView () { clearSelectedMessageIds(); } + if (sendButton != null) { + sendButton.getSlowModeCounterController(tdlib).setCurrentChat(getChatId()); + sendButton.getSlowModeCounterController(tdlib).setSlowModeCounterUpdateListener(this::onSlowModeCounterUpdate); + } + if (messageSenderButton != null) { + messageSenderButton.setInSlowMode(tdlib.inSlowMode(getChatId())); + } clearSwitchPmButton(); clearReply(); @@ -3002,6 +3028,13 @@ public void updateInputHint () { } private void updateBottomBar (boolean isUpdate) { + setInputBlockFlag(FLAG_INPUT_TEXT_DISABLED, !tdlib.canSendBasicMessage(chat)); + if (sendButton != null) { + sendButton.getSlowModeCounterController(tdlib).updateSlowModeTimer(isUpdate); + } + if (messageSenderButton != null) { + messageSenderButton.setInSlowMode(tdlib.inSlowMode(getChatId())); + } if (isUpdate) { updateInputHint(); } @@ -3047,7 +3080,7 @@ private void updateBottomBar (boolean isUpdate) { showActionJoinChatButton(); } else if (messageThread != null) { CharSequence restrictionStatus = tdlib.getBasicMessageRestrictionText(chat); - if (restrictionStatus != null) { + if (restrictionStatus != null && !hasSendSomeMediaPermission()) { showActionButton(restrictionStatus, ACTION_EMPTY, false); } else { hideActionButton(); @@ -3059,7 +3092,7 @@ private void updateBottomBar (boolean isUpdate) { showActionBotButton(); } else { CharSequence restrictionStatus = tdlib.getBasicMessageRestrictionText(chat); - if (restrictionStatus != null) { + if (restrictionStatus != null && !hasSendSomeMediaPermission()) { showActionButton(restrictionStatus, ACTION_EMPTY, false); } else { hideActionButton(); @@ -3091,13 +3124,32 @@ private void showBottomHint (CharSequence text, boolean isError) { .ignoreViewScale(true) .controller(this) .show(tdlib, text); + tooltipInfo.addOnCloseListener(this::onTooltipInfoClose); } else { tooltipInfo.reset(context().tooltipManager().newContent(tdlib, text, 0), isError ? R.drawable.baseline_warning_24 : 0); tooltipInfo.show(); } + isSlowModeRestrictionHintVisible = false; tooltipInfo.hideDelayed(false); } + private boolean isSlowModeRestrictionHintVisible; + + private void onTooltipInfoClose (long duration) { + isSlowModeRestrictionHintVisible = false; + } + + private void onSlowModeCounterUpdate (int duration) { + if (sendButton != null && tooltipInfo != null && tooltipInfo.isVisible() && isSlowModeRestrictionHintVisible) { + CharSequence restriction = tdlib().getSlowModeRestrictionText(getChatId(), null); + if (restriction != null) { + tooltipInfo.reset(context().tooltipManager().newContent(tdlib, restriction, 0), R.drawable.baseline_warning_24); + } else { + tooltipInfo.hideNow(); + } + } + } + @Override public void onPreferVideoModeChanged (boolean preferVideoMode) { if (!sendShown.getValue()) { @@ -3732,7 +3784,7 @@ public void onAccountSwitched (TdlibAccount newAccount, TdApi.User profile, int private static HashSet shownTutorials; private void showMessageMenuTutorial () { - if (sendShown.getValue() && !areScheduledOnly() && !isInputLess() && canWriteMessages() && hasWritePermission() && !isEditingMessage() && !isSecretChat() && isFocused() && !isVoicePreviewShowing() && !sendButton.inInlineMode()) { + if (sendShown.getValue() && !areScheduledOnly() && !isInputLess() && canWriteMessages() && hasSendBasicMessagePermission() && !isEditingMessage() && !isSecretChat() && isFocused() && !isVoicePreviewShowing() && !sendButton.inInlineMode()) { long tutorialFlag; if (isSelfChat()) { tutorialFlag = Settings.TUTORIAL_SET_REMINDER; @@ -4109,6 +4161,10 @@ public void destroy () { reactionsButton.performDestroy(); } + if (sendButton != null) { + sendButton.destroySlowModeCounterController(); + } + // messagesView.clear(); closeVoicePreview(true); @@ -4263,7 +4319,7 @@ public void showMore () { ids.append(R.id.btn_sendScreenshotNotification); strings.append("Send screenshot notification"); } - if (!hasWritePermission()) { + if (!hasSendBasicMessagePermission()) { ids.append(R.id.btn_debugShowHideBottomBar); strings.append("Show/hide bottom bar"); } @@ -5178,7 +5234,7 @@ public boolean isSelfChat () { } @Deprecated - public boolean hasWritePermission () { + private boolean hasWritePermission () { // FIXME: this check is outdated and no longer correct return chat != null && tdlib.canSendBasicMessage(chat) && !isEventLog(); } @@ -5189,6 +5245,18 @@ public boolean canSendPhotosAndVideos () { // FIXME separate photos and videos tdlib.canSendMessage(chat, RightId.SEND_VIDEOS); } + public boolean hasSendMessagePermission (@RightId int rightId) { + return chat != null && tdlib.canSendMessage(chat, rightId) && !isEventLog(); + } + + public boolean hasSendBasicMessagePermission () { + return chat != null && tdlib.canSendBasicMessage(chat) && !isEventLog(); + } + + public boolean hasSendSomeMediaPermission () { + return chat != null && tdlib.canSendSendSomeMedia(chat) && !isEventLog(); + } + // test private OptionDelegate newMessageOptionDelegate (final MessageContext context) { @@ -5269,7 +5337,9 @@ private OptionDelegate newMessageOptionDelegate (final TGMessage selectedMessage return true; } else if (id == R.id.btn_messageSendNow) { cancelSheduledKeyboardOpeningAndHideAllKeyboards(); - tdlib.client().send(new TdApi.EditMessageSchedulingState(getChatId(), selectedMessage.getId(), null), tdlib.okHandler()); + if (!showRestriction(null, tdlib.getSlowModeRestrictionText(getChatId()))) { + tdlib.client().send(new TdApi.EditMessageSchedulingState(getChatId(), selectedMessage.getId(), null), tdlib.okHandler()); + } return true; } else if (id == R.id.btn_messageReschedule) { cancelSheduledKeyboardOpeningAndHideAllKeyboards(); @@ -6634,6 +6704,7 @@ public boolean inSimpleSendMode () { private static final int FLAG_INPUT_EDITING = 1; private static final int FLAG_INPUT_OFFSCREEN = 1 << 1; private static final int FLAG_INPUT_RECORDING = 1 << 2; + private static final int FLAG_INPUT_TEXT_DISABLED = 1 << 3; private int inputBlockFlags; @@ -6652,8 +6723,11 @@ private boolean setInputBlockFlags (int flags) { private void setInputBlockFlag (int flag, boolean active) { if (setInputBlockFlags(BitwiseUtils.setFlag(inputBlockFlags, flag, active))) { - if (flag == FLAG_INPUT_OFFSCREEN && inputView != null) { - inputView.setEnabled(!active); + if ((flag == FLAG_INPUT_OFFSCREEN || flag == FLAG_INPUT_TEXT_DISABLED) && inputView != null) { + inputView.setEnabled( + !BitwiseUtils.hasFlag(inputBlockFlags, FLAG_INPUT_OFFSCREEN) && + !BitwiseUtils.hasFlag(inputBlockFlags, FLAG_INPUT_TEXT_DISABLED) + ); } } } @@ -7826,7 +7900,7 @@ public void switchInline (long viaBotUserId, final TdApi.InlineKeyboardButtonTyp final String username = Td.primaryUsername(user); - if (switchInline.targetChat.getConstructor() == TdApi.TargetChatCurrent.CONSTRUCTOR && canWriteMessages() && hasWritePermission()) { // FIXME rightId.SEND_OTHER_MESSAGES + if (switchInline.targetChat.getConstructor() == TdApi.TargetChatCurrent.CONSTRUCTOR && canWriteMessages() && hasSendMessagePermission(RightId.SEND_OTHER_MESSAGES)) { if (inputView != null) { inputView.setInput("@" + username + " " + switchInline.query, true, true); } @@ -8077,16 +8151,6 @@ protected int makeGuessAboutForcePreviewHeight () { return getForcePreviewHeight(/* hasHeader */ true, /* hasFooter */ true); } - /*public int getForceTouchModeOffset () { - int height = Screen.currentHeight() - HeaderView.getSize(true); - - if (tdlib.hasWritePermission(chat) || (tdlib.isChannel(chat.id) && !TD.isMember(tdlib.chatStatus(chat.id)))) { - height -= Screen.dp(49f); - } - - return (height - makeGuessAboutForcePreviewHeight()); - }*/ - // Commands public boolean getCommandsState () { @@ -8437,7 +8501,7 @@ private boolean sendContent (View view, int rightId, int defaultRes, int specifi } private boolean showGifRestriction (View view) { - return showRestriction(view, RightId.SEND_OTHER_MESSAGES, R.string.ChatDisabledStickers, R.string.ChatRestrictedStickers, R.string.ChatRestrictedStickersUntil); + return showSlowModeRestriction(view, null) || showRestriction(view, RightId.SEND_OTHER_MESSAGES, R.string.ChatDisabledStickers, R.string.ChatRestrictedStickers, R.string.ChatRestrictedStickersUntil); } public boolean showPhotoVideoRestriction (View view) { // TODO separate photos & videos @@ -8450,6 +8514,11 @@ public boolean showPhotoVideoRestriction (View view, boolean checkPhotos, boolea if (photosStatus == null && videosStatus == null) { return false; } + + if (showSlowModeRestriction(view, null)) { + return true; + } + if (videosStatus == null || (videosStatus.isGlobal() && photosStatus != null && !photosStatus.isGlobal())) { // photo return showRestriction(view, RightId.SEND_PHOTOS, R.string.ChatDisabledPhoto, R.string.ChatRestrictedPhoto, R.string.ChatRestrictedPhotoUntil); @@ -8466,6 +8535,21 @@ public boolean showRestriction (View view, @RightId int rightId) { return showRestriction(view, text); } + public boolean showSlowModeRestriction (View v, @Nullable TdApi.MessageSendOptions sendOptions) { + CharSequence restriction = tdlib().getSlowModeRestrictionText(getChatId(), sendOptions != null ? sendOptions.schedulingState : null); + if (restriction != null) { + if (v == sendButton || v == recordButton) { + showBottomHint(restriction, true); + isSlowModeRestrictionHintVisible = true; + return true; + } + showRestriction(v, restriction); + return true; + } + + return false; + } + public boolean showRestriction (View view, CharSequence restrictionText) { if (restrictionText != null) { if (view == sendButton || view == recordButton) { @@ -8486,7 +8570,7 @@ public boolean showRestriction (View view, @RightId int rightId, int defaultRes, } private boolean sendContent (View view, @RightId int rightId, int defaultRes, int specificRes, int specificUntilRes, Future replyTo, TdApi.MessageSendOptions initialSendOptions, Future content) { - if (showRestriction(view, rightId, defaultRes, specificRes, specificUntilRes)) + if (showSlowModeRestriction(view, initialSendOptions) || showRestriction(view, rightId, defaultRes, specificRes, specificUntilRes)) return false; pickDateOrProceed(initialSendOptions, (modifiedSendOptions, disableMarkdown) -> { tdlib.sendMessage(chat.id, getMessageThreadId(), replyTo != null ? replyTo.getValue() : null, Td.newSendOptions(modifiedSendOptions, obtainSilentMode()), content.getValue(), null); @@ -9034,7 +9118,11 @@ private void setIsSendingText (boolean isSendingText) { } private void sendText (TdApi.FormattedText msg, boolean clearInput, boolean allowDice, boolean allowReply, boolean allowLinkPreview, TdApi.MessageSendOptions initialSendOptions) { - if ((Td.isEmpty(msg) && !(clearInput && inputView != null && inputView.getText().length() > 0)) || !hasWritePermission() || (isSendingText && clearInput)) { + if ((Td.isEmpty(msg) && !(clearInput && inputView != null && inputView.getText().length() > 0)) || (isSendingText && clearInput)) { + return; + } + if (!hasSendBasicMessagePermission()) { + context().tooltipManager().builder(sendButton != null ? sendButton : inputView).show(tdlib, R.string.MessageInputTextDisabledHint).hideDelayed(); return; } @@ -9073,6 +9161,10 @@ private void sendText (TdApi.FormattedText msg, boolean clearInput, boolean allo List functions = TD.sendMessageText(chatId, messageThreadId, replyTo, finalSendOptions, content, tdlib.maxMessageTextLength()); final boolean isSchedule = finalSendOptions.schedulingState != null; + if (showSlowModeRestriction(sendButton != null ? sendButton : inputView, finalSendOptions)) { + return; + } + if (clearInput) { final int expectedCount = functions.size(); final List sentMessages = new ArrayList<>(expectedCount); @@ -9163,7 +9255,7 @@ public void onResult (TdApi.Object result) { } public void sendContact (TdApi.User user, boolean allowReply, TdApi.MessageSendOptions initialSendOptions) { - if (hasWritePermission()) { + if (hasSendMessagePermission(RightId.SEND_BASIC_MESSAGES)) { pickDateOrProceed(initialSendOptions, (modifiedSendOptions, disableMarkdown) -> { tdlib.sendMessage(chat.id, getMessageThreadId(), @@ -9181,7 +9273,7 @@ public void shareMyContact (boolean allowReply) { } public void shareMyContact (@Nullable TdApi.InputMessageReplyTo forceReplyTo) { - if (hasWritePermission()) { + if (hasSendMessagePermission(RightId.SEND_BASIC_MESSAGES)) { TdApi.User user = tdlib.myUser(); if (user != null) { pickDateOrProceed(Td.newSendOptions(), (modifiedSendOptions, disableMarkdown) -> { @@ -9192,7 +9284,7 @@ public void shareMyContact (@Nullable TdApi.InputMessageReplyTo forceReplyTo) { } public void send (TdApi.InputMessageContent content, boolean allowReply, TdApi.MessageSendOptions initialSendOptions, RunnableData after) { - if (hasWritePermission()) { // FIXME RightId.SEND_POLLS + if (tdlib().getRestrictionText(chat, content) == null) { pickDateOrProceed(initialSendOptions, (modifiedSendOptions, disableMarkdown) -> { tdlib.sendMessage(chat.id, getMessageThreadId(), allowReply ? obtainReplyTo() : null, Td.newSendOptions(modifiedSendOptions, obtainSilentMode()), content, after); }); @@ -9200,7 +9292,7 @@ public void send (TdApi.InputMessageContent content, boolean allowReply, TdApi.M } public void sendInlineQueryResult (long inlineQueryId, String id, boolean allowReply, boolean clearInput, TdApi.MessageSendOptions initialSendOptions) { - if (hasWritePermission()) { // FIXME RightId.SEND_OTHER + if (hasSendMessagePermission(RightId.SEND_OTHER_MESSAGES)) { pickDateOrProceed(initialSendOptions, (modifiedSendOptions, disableMarkdown) -> { tdlib.sendInlineQueryResult(chat.id, getMessageThreadId(), allowReply ? obtainReplyTo() : null, Td.newSendOptions(modifiedSendOptions, obtainSilentMode()), inlineQueryId, id); if (clearInput) { @@ -9212,7 +9304,7 @@ public void sendInlineQueryResult (long inlineQueryId, String id, boolean allowR } public void sendAudio (TdApi.Audio audio, boolean allowReply) { - if (hasWritePermission()) { + if (hasSendMessagePermission(RightId.SEND_AUDIO)) { pickDateOrProceed(Td.newSendOptions(), (modifiedSendOptions, disableMarkdown) -> { tdlib.sendMessage(chat.id, getMessageThreadId(), allowReply ? obtainReplyTo() : null, Td.newSendOptions(modifiedSendOptions, obtainSilentMode()), TD.toInputMessageContent(audio), null); }); @@ -9220,7 +9312,7 @@ public void sendAudio (TdApi.Audio audio, boolean allowReply) { } public void sendMusic (View view, List musicFiles, boolean needGroupMedia, boolean allowReply, TdApi.MessageSendOptions initialSendOptions) { - if (!showRestriction(view, RightId.SEND_AUDIO)) { + if (!showSlowModeRestriction(view, initialSendOptions) && !showRestriction(view, RightId.SEND_AUDIO)) { TdApi.InputMessageContent[] content = new TdApi.InputMessageContent[musicFiles.size()]; for (int i = 0; i < content.length; i++) { MediaBottomFilesController.MusicEntry musicFile = musicFiles.get(i); @@ -9235,7 +9327,7 @@ public void sendMusic (View view, List mu } public boolean sendRecord (View view, final TGRecord record, boolean allowReply, TdApi.MessageSendOptions initialSendOptions) { - if (showRestriction(view, RightId.SEND_VOICE_NOTES)) { + if (showSlowModeRestriction(view, initialSendOptions) || showRestriction(view, RightId.SEND_VOICE_NOTES)) { return false; } final long chatId = chat.id; @@ -9253,7 +9345,7 @@ public boolean sendRecord (View view, final TGRecord record, boolean allowReply, } public void forwardMessage (TdApi.Message message) { // TODO remove all related to Forward stuff to replace with ShareLayout - if (hasWritePermission()) { + if (tdlib.getRestrictionText(chat, message) == null) { tdlib.forwardMessage(chat.id, getMessageThreadId(), message.chatId, message.id, Td.newSendOptions(obtainSilentMode())); } } @@ -9343,7 +9435,7 @@ public void onActivityResult (int requestCode, int resultCode, Intent data) { case Intents.ACTIVITY_RESULT_VIDEO_CAPTURE: { File file = Intents.takeLastOutputMedia(); boolean isVideo = requestCode == Intents.ACTIVITY_RESULT_VIDEO_CAPTURE; - if (showRestriction(mediaButton, isVideo ? RightId.SEND_VIDEOS : RightId.SEND_PHOTOS)) { + if (showSlowModeRestriction(mediaButton, null) || showRestriction(mediaButton, isVideo ? RightId.SEND_VIDEOS : RightId.SEND_PHOTOS)) { return; } if (file != null) { @@ -9408,7 +9500,7 @@ stack, areScheduledOnly() case Intents.ACTIVITY_RESULT_AUDIO: { final Uri path = data.getData(); if (path == null) break; - if (showRestriction(mediaButton, RightId.SEND_AUDIO)) { + if (showSlowModeRestriction(mediaButton, null) || showRestriction(mediaButton, RightId.SEND_AUDIO)) { return; } final String audioPath = U.tryResolveFilePath(path); @@ -9431,6 +9523,10 @@ stack, areScheduledOnly() } public void sendFiles (View view, final List paths, boolean needGroupMedia, boolean allowReply, TdApi.MessageSendOptions initialSendOptions) { + if (showSlowModeRestriction(view, initialSendOptions)) { + return; + } + final long chatId = chat.id; final boolean isSecretChat = isSecretChat(); final TdApi.MessageSendOptions finalSendOptions = Td.newSendOptions(initialSendOptions, obtainSilentMode()); @@ -9471,7 +9567,7 @@ public void sendFiles (View view, final List paths, boolean needGroupMed } public void sendPhotoCompressed (final String path, final @Nullable TdApi.MessageSelfDestructType selfDestructType, final boolean allowReply) { - if (showRestriction(mediaButton, RightId.SEND_PHOTOS)) { + if (showSlowModeRestriction(mediaButton, null) || showRestriction(mediaButton, RightId.SEND_PHOTOS)) { return; } if (StringUtils.isEmpty(path)) { @@ -9970,6 +10066,15 @@ public void onChatHasScheduledMessagesChanged (long chatId, boolean hasScheduled }); } + @Override + public void onChatPermissionsChanged (long chatId, TdApi.ChatPermissions permissions) { + tdlib.ui().post(() -> { + if (getChatId() == chatId) { + updateBottomBar(true); + } + }); + } + @Override public void onChatReadInbox(final long chatId, final long lastReadInboxMessageId, final int unreadCount, boolean availabilityChanged) { tdlib.ui().post(() -> { @@ -10131,6 +10236,9 @@ public void onSupergroupFullUpdated (final long supergroupId, final TdApi.Superg } if (ChatId.toSupergroupId(getChatId()) == supergroupId) { checkLinkedChat(); + if (messageSenderButton != null) { + messageSenderButton.setInSlowMode(tdlib.inSlowMode(getChatId())); + } } }); } @@ -10289,6 +10397,9 @@ private boolean sendShowingVoice (View view, TdApi.MessageSendOptions sendOption if (!isVoiceShowing) { return false; } + if (showSlowModeRestriction(view, sendOptions) || showRestriction(view, RightId.SEND_VOICE_NOTES)) { + return false; + } TGRecord record = voiceInputView.getRecord(); if (record != null) { voiceInputView.ignoreStop(); @@ -10793,7 +10904,7 @@ private void setSearchControlsFactor (float factor) { } private boolean needSearchControlsTranslate () { - return tdlib.isChannelChat(chat) && !hasWritePermission(); + return tdlib.isChannelChat(chat) && !canWriteMessages(); } private float getSearchControlsOffset () { diff --git a/app/src/main/java/org/thunderdog/challegram/ui/ProfileController.java b/app/src/main/java/org/thunderdog/challegram/ui/ProfileController.java index df4671c7c1..05b0dfcf90 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/ProfileController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/ProfileController.java @@ -365,8 +365,11 @@ private void replaceWithSupergroup (long supergroupId) { // Controller + private final TdlibUi.AvatarPickerManager avatarPickerManager; + public ProfileController (Context context, Tdlib tdlib) { super(context, tdlib); + avatarPickerManager = new TdlibUi.AvatarPickerManager(this); } @Override @@ -4415,84 +4418,11 @@ private void joinChannel () { @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - switch (requestCode) { - case Intents.ACTIVITY_RESULT_IMAGE_CAPTURE: { - File image = Intents.takeLastOutputMedia(); - if (image != null) { - // TODO show editor - U.addToGallery(image); - UI.showToast(R.string.UploadingPhotoWait, Toast.LENGTH_SHORT); - tdlib.client().send(new TdApi.SetChatPhoto(chat.id, new TdApi.InputChatPhotoStatic(new TdApi.InputFileGenerated(image.getPath(), SimpleGenerationInfo.makeConversion(image.getPath()), 0))), tdlib.okHandler()); - } - - break; - } - case Intents.ACTIVITY_RESULT_GALLERY: { - Uri image = data.getData(); - if (image == null) break; - final Uri path = data.getData(); - String imagePath = U.tryResolveFilePath(path); - - if (imagePath == null) break; - - if (imagePath.endsWith(".webp")) { - UI.showToast("Webp is not supported for profile photos", Toast.LENGTH_LONG); - break; - } - - UI.showToast(R.string.UploadingPhotoWait, Toast.LENGTH_SHORT); - tdlib.client().send(new TdApi.SetChatPhoto(chat.id, new TdApi.InputChatPhotoStatic(new TdApi.InputFileGenerated(imagePath, SimpleGenerationInfo.makeConversion(imagePath), 0))), tdlib.okHandler()); - - break; - } - } + avatarPickerManager.handleActivityResult(requestCode, resultCode, data, TdlibUi.AvatarPickerManager.MODE_CHAT, chat, null); } private void changeProfilePhoto () { - IntList ids = new IntList(4); - StringList strings = new StringList(4); - IntList colors = new IntList(4); - IntList icons = new IntList(4); - - if (chat != null && chat.photo != null && !isEditing()) { - ids.append(R.id.btn_open); - strings.append(R.string.Open); - icons.append(R.drawable.baseline_visibility_24); - colors.append(OPTION_COLOR_NORMAL); - } - - ids.append(R.id.btn_changePhotoCamera); - strings.append(R.string.ChatCamera); - icons.append(R.drawable.baseline_camera_alt_24); - colors.append(OPTION_COLOR_NORMAL); - - ids.append(R.id.btn_changePhotoGallery); - strings.append(R.string.Gallery); - icons.append(R.drawable.baseline_image_24); - colors.append(OPTION_COLOR_NORMAL); - - if (chat != null && chat.photo != null) { - ids.append(R.id.btn_changePhotoDelete); - strings.append(R.string.Delete); - icons.append(R.drawable.baseline_delete_24); - colors.append(OPTION_COLOR_RED); - } - - showOptions(null, ids.get(), strings.get(), colors.get(), icons.get(), (itemView, id) -> { - if (id == R.id.btn_open) { - openPhoto(); - } else if (id == R.id.btn_changePhotoCamera) { - UI.openCameraDelayed(context); - } else if (id == R.id.btn_changePhotoGallery) { - UI.openGalleryDelayed(context, false); - } else if (id == R.id.btn_changePhotoDelete) { - tdlib.client().send(new TdApi.SetChatPhoto(chat.id, null), tdlib.okHandler()); - } - return true; - }); + avatarPickerManager.showMenuForChat(chat, headerCell, !isEditing()); } @Override diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingHolder.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingHolder.java index dfebadd515..0851f24a13 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingHolder.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingHolder.java @@ -1260,6 +1260,7 @@ public void getItemOffsets (Rect outRect, View view, RecyclerView parent, Recycl settingView.setOnClickListener(onClickListener); if (viewType == ListItem.TYPE_RADIO_SETTING_WITH_NEGATIVE_STATE) { settingView.getToggler().setUseNegativeState(true); + adapter.modifySettingView(viewType, settingView); } return new SettingHolder(settingView); } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsAdapter.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsAdapter.java index 714ce0c6d4..265585eaf2 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsAdapter.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsAdapter.java @@ -1796,6 +1796,7 @@ public void updateView (SettingHolder holder, int position, int viewType) { case ListItem.TYPE_RADIO_SETTING_WITH_NEGATIVE_STATE: { SettingView settingView = (SettingView) holder.itemView; settingView.setName(item.getString()); + settingView.setIcon(item.getIconResource()); settingView.getToggler().checkRtl(true); holder.itemView.setEnabled(true); setValuedSetting(item, (SettingView) holder.itemView, false); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java index f141cc0204..c5feeca9ca 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBlockedController.java @@ -221,7 +221,7 @@ protected void setUser (ListItem item, int position, UserView userView, boolean } }; buildCells(); - ViewSupport.setThemedBackground(recyclerView, ColorId.filling, this); + // ViewSupport.setThemedBackground(recyclerView, ColorId.filling, this); RemoveHelper.attach(recyclerView, new RemoveHelper.Callback() { @Override public boolean canRemove (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int position) { diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsController.java index 03cf707965..aa7ca5fb2b 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsController.java @@ -43,7 +43,6 @@ import org.thunderdog.challegram.data.TGStickerSetInfo; import org.thunderdog.challegram.loader.AvatarReceiver; import org.thunderdog.challegram.loader.ImageGalleryFile; -import org.thunderdog.challegram.mediaview.MediaViewController; import org.thunderdog.challegram.navigation.ActivityResultHandler; import org.thunderdog.challegram.navigation.BackHeaderButton; import org.thunderdog.challegram.navigation.ComplexHeaderView; @@ -99,12 +98,15 @@ public class SettingsController extends ViewController implements Menu, MoreDelegate, OptionDelegate, TdlibCache.MyUserDataChangeListener, ConnectionListener, StickersListener, MediaLayout.MediaGalleryCallback, ActivityResultHandler, View.OnLongClickListener, SessionListener, GlobalTokenStateListener { + + private final TdlibUi.AvatarPickerManager avatarPickerManager; private ComplexHeaderView headerCell; private ComplexRecyclerView contentView; private SettingsAdapter adapter; public SettingsController (Context context, Tdlib tdlib) { super(context, tdlib); + avatarPickerManager = new TdlibUi.AvatarPickerManager(this); } @Override @@ -248,56 +250,12 @@ public View getCustomHeaderCell () { } private void changeProfilePhoto () { - IntList ids = new IntList(4); - StringList strings = new StringList(4); - IntList colors = new IntList(4); - IntList icons = new IntList(4); - - final TdApi.User user = tdlib.myUser(); - if (user != null && user.profilePhoto != null) { - ids.append(R.id.btn_open); - strings.append(R.string.Open); - icons.append(R.drawable.baseline_visibility_24); - colors.append(OPTION_COLOR_NORMAL); - } - - ids.append(R.id.btn_changePhotoCamera); - strings.append(R.string.ChatCamera); - icons.append(R.drawable.baseline_camera_alt_24); - colors.append(OPTION_COLOR_NORMAL); - - ids.append(R.id.btn_changePhotoGallery); - strings.append(R.string.Gallery); - icons.append(R.drawable.baseline_image_24); - colors.append(OPTION_COLOR_NORMAL); - - final long profilePhotoToDelete = user != null && user.profilePhoto != null ? user.profilePhoto.id : 0; - if (user != null && user.profilePhoto != null) { - ids.append(R.id.btn_changePhotoDelete); - strings.append(R.string.Delete); - icons.append(R.drawable.baseline_delete_24); - colors.append(OPTION_COLOR_RED); - } - - showOptions(null, ids.get(), strings.get(), colors.get(), icons.get(), (itemView, id) -> { - if (id == R.id.btn_open) { - MediaViewController.openFromProfile(SettingsController.this, user, headerCell); - } else if (id == R.id.btn_changePhotoCamera) { - UI.openCameraDelayed(context); - } else if (id == R.id.btn_changePhotoGallery) { - UI.openGalleryDelayed(context, false); - } else if (id == R.id.btn_changePhotoDelete) { - tdlib.client().send(new TdApi.DeleteProfilePhoto(profilePhotoToDelete), tdlib.okHandler()); - } - return true; - }); + avatarPickerManager.showMenuForProfile(headerCell, false); } @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK) { - tdlib.ui().handlePhotoChange(requestCode, data, null); - } + avatarPickerManager.handleActivityResult(requestCode, resultCode, data, TdlibUi.AvatarPickerManager.MODE_PROFILE, null, null); } private boolean hasNotificationError; diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java index 0f7de66048..f1ea711f1e 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyKeyController.java @@ -15,6 +15,7 @@ package org.thunderdog.challegram.ui; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -30,14 +31,17 @@ import org.thunderdog.challegram.component.dialogs.SearchManager; import org.thunderdog.challegram.core.Lang; import org.thunderdog.challegram.data.TGUser; +import org.thunderdog.challegram.navigation.ActivityResultHandler; import org.thunderdog.challegram.telegram.PrivacySettings; import org.thunderdog.challegram.telegram.PrivacySettingsListener; import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.telegram.TdlibCache; import org.thunderdog.challegram.telegram.TdlibUi; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.tool.Fonts; import org.thunderdog.challegram.tool.Screen; import org.thunderdog.challegram.tool.UI; +import org.thunderdog.challegram.util.ProfilePhotoDrawModifier; import org.thunderdog.challegram.util.CustomTypefaceSpan; import org.thunderdog.challegram.util.NoUnderlineClickableSpan; import org.thunderdog.challegram.util.UserPickerMultiDelegate; @@ -51,7 +55,8 @@ import me.vkryl.td.ChatId; import me.vkryl.td.Td; -public class SettingsPrivacyKeyController extends RecyclerViewController implements View.OnClickListener, UserPickerMultiDelegate, PrivacySettingsListener { +public class SettingsPrivacyKeyController extends RecyclerViewController implements View.OnClickListener, UserPickerMultiDelegate, PrivacySettingsListener, ActivityResultHandler, + TdlibCache.UserDataChangeListener { public SettingsPrivacyKeyController (Context context, Tdlib tdlib) { super(context, tdlib); @@ -342,6 +347,13 @@ private void buildCells () { items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.CustomShareSettingsHelp)); } + if (getArgumentsStrict().getConstructor() == TdApi.UserPrivacySettingShowProfilePhoto.CONSTRUCTOR) { + items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); + items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_setProfilePhoto, 0, R.string.PublicPhoto)); + items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.PublicPhotoHint)); + } + /*if (privacyKey.getConstructor() == TdApi.UserPrivacySettingAllowCalls.CONSTRUCTOR) { items.add(new SettingItem(SettingItem.TYPE_HEADER, 0, 0, R.string.PrivacyCallsP2PTitle)); items.add(new SettingItem(SettingItem.TYPE_SHADOW_TOP)); @@ -456,6 +468,8 @@ public void onPrivacySettingRulesChanged (TdApi.UserPrivacySetting setting, TdAp }); } + private long subscribedToUserId; + @Override protected void onCreateView (Context context, CustomRecyclerView recyclerView) { adapter = new SettingsAdapter(this) { @@ -469,6 +483,16 @@ protected void setValuedSetting (ListItem item, SettingView view, boolean isUpda int count = currentRules().getMinusTotalCount(tdlib); view.setData(count > 0 ? Lang.plural(R.string.xUsers, count) : Lang.getString(R.string.PrivacyAddUsers)); } + + if (itemId == R.id.btn_setProfilePhoto) { + final TdApi.UserFullInfo myUserFull = tdlib.myUserFull(); + final boolean hasAvatar = myUserFull != null && myUserFull.publicPhoto != null; + + view.setData(Lang.getString(hasAvatar ? R.string.PublicPhotoSet : R.string.PublicPhotoNoSet)); + view.setDrawModifier(new ProfilePhotoDrawModifier().requestFiles(view.getComplexReceiver(), tdlib)); + } else { + view.setDrawModifier(null); + } } @Override @@ -493,9 +517,21 @@ protected void modifyHeaderTextView (TextView textView, int viewHeight, int padd } } })); + + subscribedToUserId = tdlib.myUserId(); + tdlib.cache().addUserDataListener(subscribedToUserId, this); tdlib.listeners().subscribeToPrivacyUpdates(this); } + @Override + public void onUserFullUpdated (long userId, TdApi.UserFullInfo userFull) { + UI.post(() -> { + if (userId == tdlib.myUserId() && !isDestroyed()) { + adapter.updateValuedSettingById(R.id.btn_setProfilePhoto); + } + }); + } + @Override public void onBlur () { super.onBlur(); @@ -519,6 +555,7 @@ private void saveChanges () { public void destroy () { super.destroy(); tdlib.listeners().unsubscribeFromPrivacyUpdates(this); + tdlib.cache().removeUserDataListener(subscribedToUserId, this); } private int userPickMode; @@ -600,6 +637,22 @@ public void onClick (View v) { changedPrivacyRules = PrivacySettings.valueOf(currentRules().toggleGlobal(desiredMode)); updateRulesState(changedPrivacyRules); } + } else if (viewId == R.id.btn_setProfilePhoto) { + getAvatarPickerManager().showMenuForProfile(null, true); + } + } + + @Override + public void onActivityResult (int requestCode, int resultCode, Intent data) { + getAvatarPickerManager().handleActivityResult(requestCode, resultCode, data, TdlibUi.AvatarPickerManager.MODE_PROFILE_PUBLIC, null, null); + } + + private TdlibUi.AvatarPickerManager avatarPickerManager; + + private TdlibUi.AvatarPickerManager getAvatarPickerManager () { + if (avatarPickerManager == null) { + avatarPickerManager = new TdlibUi.AvatarPickerManager(this); } + return avatarPickerManager; } } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/ShareController.java b/app/src/main/java/org/thunderdog/challegram/ui/ShareController.java index 0f57b7481c..2d2368d28a 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/ShareController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/ShareController.java @@ -932,7 +932,7 @@ public void onThemeColorsChanged (boolean areTemp, ColorState state) { public boolean accept (TdApi.Chat chat) { if (tdlib.chatAvailable(chat)) { Tdlib.RestrictionStatus restrictionStatus = tdlib.getRestrictionStatus(chat, RightId.SEND_BASIC_MESSAGES); - return restrictionStatus == null || !restrictionStatus.isGlobal(); + return restrictionStatus == null || !restrictionStatus.isGlobal() || tdlib.canSendSendSomeMedia(chat, true); } return false; } @@ -1610,13 +1610,25 @@ protected boolean onFoundChatClick (View view, TGFoundChat chat) { } final long chatId = chat.getAnyId(); + final TGFoundChat finalChat = chat; + final RunnableBool after = (b) -> { + if (b) { + onFoundChatClickAfter(finalChat); + } + }; + if (!isChecked(chatId)) { if (processSingleTap(chat)) return true; - if (!toggleChecked(view, chat, null)) + if (!toggleChecked(view, chat, after)) return true; + } else { + onFoundChatClickAfter(chat); } + return true; + } + private void onFoundChatClickAfter (TGFoundChat chat) { int i = adapter.indexOfViewByLongId(chat.getAnyId()); if (i != -1) { View itemView = recyclerView.getLayoutManager().findViewByPosition(i); @@ -1649,7 +1661,6 @@ protected boolean onFoundChatClick (View view, TGFoundChat chat) { after.run(); } } - return true; } @Override @@ -1757,6 +1768,12 @@ private boolean hasVoiceOrVideoMessageContent () { private CharSequence getErrorMessage (long chatId) { Args args = getArgumentsStrict(); TdApi.Chat chat = tdlib.chatStrict(chatId); + + CharSequence slowModeRestrictionText = tdlib().getSlowModeRestrictionText(chatId); + if (slowModeRestrictionText != null) { + return slowModeRestrictionText; + } + switch (mode) { case MODE_TEXT: { return tdlib.getBasicMessageRestrictionText(chat); @@ -1863,18 +1880,27 @@ private boolean toggleCheckedImpl (View view, TGFoundChat chat, @Nullable Runnab boolean result = !isChecked(chatId); if (result) { - if (performAsyncChecks && ChatId.isUserChat(chatId) && hasVoiceOrVideoMessageContent()) { - lockedChatIds.add(chatId); - tdlib.cache().userFull(tdlib.chatUserId(chatId), userFullInfo -> { - lockedChatIds.remove(chatId); - // FIXME: view recycling safety - // By the time `after` is called, initial view could have been already recycled. - // Current implementation relies on the quick response from GetUserFull, - // however, there's a chance `view` could have been already taken by some other view. - // Should be fixed inside `after` contents. - toggleCheckedImpl(view, chat, after, false); - }); - return false; + if (performAsyncChecks) { + // FIXME: view recycling safety + // By the time `after` is called, initial view could have been already recycled. + // Current implementation relies on the quick response from GetUserFull, + // however, there's a chance `view` could have been already taken by some other view. + // Should be fixed inside `after` contents. + if (ChatId.isUserChat(chatId) && hasVoiceOrVideoMessageContent()) { + lockedChatIds.add(chatId); + tdlib.cache().userFull(tdlib.chatUserId(chatId), userFullInfo -> { + lockedChatIds.remove(chatId); + toggleCheckedImpl(view, chat, after, false); + }); + return false; + } else if (ChatId.isSupergroup(chatId)) { + lockedChatIds.add(chatId); + tdlib.cache().supergroupFull(ChatId.toSupergroupId(chatId), supergroupFullInfo -> { + lockedChatIds.remove(chatId); + toggleCheckedImpl(view, chat, after, false); + }); + return false; + } } if (showErrorMessage(view, chatId, false)) { result = false; diff --git a/app/src/main/java/org/thunderdog/challegram/ui/camera/CameraController.java b/app/src/main/java/org/thunderdog/challegram/ui/camera/CameraController.java index 705a894f08..a56ef659d6 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/camera/CameraController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/camera/CameraController.java @@ -52,7 +52,9 @@ import org.thunderdog.challegram.loader.ImageGalleryFile; import org.thunderdog.challegram.loader.ImageReader; import org.thunderdog.challegram.loader.ImageStrictCache; +import org.thunderdog.challegram.mediaview.AvatarPickerMode; import org.thunderdog.challegram.mediaview.MediaSelectDelegate; +import org.thunderdog.challegram.mediaview.MediaSendDelegate; import org.thunderdog.challegram.mediaview.MediaSpoilerSendDelegate; import org.thunderdog.challegram.mediaview.MediaViewController; import org.thunderdog.challegram.mediaview.MediaViewDelegate; @@ -127,6 +129,7 @@ public interface QrCodeListener { private boolean qrCodeConfirmed; private int qrSubtitleRes; private boolean qrModeDebug; + private @AvatarPickerMode int avatarPickerMode = AvatarPickerMode.NONE; public void setQrListener (@Nullable QrCodeListener qrCodeListener, @StringRes int subtitleRes, boolean qrModeDebug) { this.qrCodeListener = qrCodeListener; @@ -138,6 +141,16 @@ public void setQrListener (@Nullable QrCodeListener qrCodeListener, @StringRes i } } + public void setAvatarPickerMode (@AvatarPickerMode int avatarPickerMode) { + this.avatarPickerMode = avatarPickerMode; + } + + public void setMediaEditorDelegates (MediaViewDelegate delegate, MediaSelectDelegate selectDelegate, MediaSendDelegate sendDelegate) { + this.delegate = delegate; + this.selectDelegate = selectDelegate; + this.sendDelegate = sendDelegate; + } + public void setMode (int mode, @Nullable ReadyListener readyListener) { this.qrCodeConfirmed = false; this.readyListener = readyListener; @@ -1455,6 +1468,10 @@ private boolean onSendMedia (ImageGalleryFile file, TdApi.MessageSendOptions opt return false; } + public MediaViewDelegate delegate; + public MediaSelectDelegate selectDelegate; + public MediaSendDelegate sendDelegate; + @Override public void onMediaTaken (final ImageGalleryFile file) { boolean awaitLayout = applyFakeRotation(); @@ -1466,7 +1483,7 @@ public void onMediaTaken (final ImageGalleryFile file) { MediaItem item = new MediaItem(context, tdlib, file); stack.set(item); MessagesController m = findOutputController(); - MediaViewController.Args args = new MediaViewController.Args(CameraController.this, MediaViewController.MODE_GALLERY, new MediaViewDelegate() { + MediaViewController.Args args = MediaViewController.Args.fromGallery(CameraController.this, delegate != null ? delegate : new MediaViewDelegate() { @Override public MediaViewThumbLocation getTargetLocation (int indexInStack, MediaItem item) { MediaViewThumbLocation location = new MediaViewThumbLocation(0, 0, contentView.getMeasuredWidth(), contentView.getMeasuredHeight()); @@ -1479,7 +1496,7 @@ public MediaViewThumbLocation getTargetLocation (int indexInStack, MediaItem ite public void setMediaItemVisible (int index, MediaItem item, boolean isVisible) { } - }, new MediaSelectDelegate() { + }, selectDelegate != null ? selectDelegate : new MediaSelectDelegate() { @Override public boolean isMediaItemSelected (int index, MediaItem item) { return false; @@ -1515,13 +1532,13 @@ public long getOutputChatId () { public ArrayList getSelectedMediaItems (boolean copy) { return null; } - }, new MediaSpoilerSendDelegate() { + }, sendDelegate != null ? sendDelegate : new MediaSpoilerSendDelegate() { @Override public boolean sendSelectedItems (View view, ArrayList images, TdApi.MessageSendOptions options, boolean disableMarkdown, boolean asFiles, boolean hasSpoiler) { ImageGalleryFile galleryFile = (ImageGalleryFile) images.get(0); return onSendMedia(galleryFile, options, disableMarkdown, asFiles, hasSpoiler); } - }, stack).setOnlyScheduled(m != null && m.areScheduledOnly()); + }, stack, m != null && m.areScheduledOnly()).setAvatarPickerMode(avatarPickerMode); if (m != null) { args.setReceiverChatId(m.getChatId()); } diff --git a/app/src/main/java/org/thunderdog/challegram/util/ProfilePhotoDrawModifier.java b/app/src/main/java/org/thunderdog/challegram/util/ProfilePhotoDrawModifier.java new file mode 100644 index 0000000000..ca7304090a --- /dev/null +++ b/app/src/main/java/org/thunderdog/challegram/util/ProfilePhotoDrawModifier.java @@ -0,0 +1,71 @@ +/* + * This file is a part of Telegram X + * Copyright © 2014 (tgx-android@pm.me) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * File created on 12/11/2023, 15:38. + */ + +package org.thunderdog.challegram.util; + +import android.graphics.Canvas; +import android.view.View; + +import org.drinkless.tdlib.TdApi; +import org.thunderdog.challegram.loader.AvatarReceiver; +import org.thunderdog.challegram.loader.ComplexReceiver; +import org.thunderdog.challegram.loader.ComplexReceiverProvider; +import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.tool.Screen; + +import me.vkryl.td.Td; + +public class ProfilePhotoDrawModifier implements DrawModifier { + @Override + public void afterDraw (View view, Canvas c) { + ComplexReceiver complexReceiver = view instanceof ComplexReceiverProvider ? ((ComplexReceiverProvider) view).getComplexReceiver() : null; + if (complexReceiver == null) return; + + AvatarReceiver avatarReceiver = complexReceiver.getAvatarReceiver(0); + if (avatarReceiver.isEmpty()) return; + + int size = Screen.dp(48); + int x = view.getMeasuredWidth() - size - Screen.dp(20); + int y = Screen.dp(8); + + avatarReceiver.setBounds(x, y, x + size, y + size); + if (avatarReceiver.needPlaceholder()) { + avatarReceiver.drawPlaceholder(c); + } + avatarReceiver.draw(c); + } + + public ProfilePhotoDrawModifier requestFiles (ComplexReceiver complexReceiver, Tdlib tdlib) { + AvatarReceiver avatarReceiver = complexReceiver.getAvatarReceiver(0); + + TdApi.UserFullInfo info = tdlib.myUserFull(); + if (info != null && info.publicPhoto != null && info.publicPhoto.sizes != null && info.publicPhoto.sizes.length > 0) { + TdApi.ChatPhotoInfo chatPhotoInfo = new TdApi.ChatPhotoInfo( + Td.findSmallest(info.publicPhoto.sizes).photo, + Td.findBiggest(info.publicPhoto.sizes).photo, + info.publicPhoto.minithumbnail, info.publicPhoto.animation != null, false); + avatarReceiver.requestSpecific(tdlib, chatPhotoInfo, AvatarReceiver.Options.NO_UPDATES); + } else { + avatarReceiver.clear(); + } + + return this; + } + + @Override + public int getWidth () { + return Screen.dp(48); + } +} diff --git a/app/src/main/java/org/thunderdog/challegram/util/text/Counter.java b/app/src/main/java/org/thunderdog/challegram/util/text/Counter.java index e67203d6d8..6780d41a67 100644 --- a/app/src/main/java/org/thunderdog/challegram/util/text/Counter.java +++ b/app/src/main/java/org/thunderdog/challegram/util/text/Counter.java @@ -15,6 +15,7 @@ package org.thunderdog.challegram.util.text; import android.graphics.Canvas; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.view.Gravity; import android.view.View; @@ -358,16 +359,28 @@ public void draw (Canvas c, float cx, float cy, int gravity, float alpha) { draw(c, cx, cy, gravity, alpha, null, 0); } + public void draw (Canvas c, float cx, float cy, int gravity, float alpha, @Nullable RectF outDrawRect) { + draw(c, cx, cy, gravity, alpha, alpha, alpha, null, 0, outDrawRect); + } + public void draw (Canvas c, float cx, float cy, int gravity, float alpha, DrawableProvider drawableProvider, @PorterDuffColorId int drawableColorId) { draw(c, cx, cy, gravity, alpha, alpha, alpha, drawableProvider, drawableColorId); } + public void draw (Canvas c, float cx, float cy, int gravity, float alpha, DrawableProvider drawableProvider, @PorterDuffColorId int drawableColorId, @Nullable RectF outputDrawRect) { + draw(c, cx, cy, gravity, alpha, alpha, alpha, drawableProvider, drawableColorId, outputDrawRect); + } + public void draw (Canvas c, float cx, float cy, int gravity, float textAlpha, float backgroundAlpha, float drawableAlpha, DrawableProvider drawableProvider, @PorterDuffColorId int drawableColorId) { + draw(c, cx, cy, gravity, textAlpha, backgroundAlpha, drawableAlpha, drawableProvider, drawableColorId, null); + } + + public void draw (Canvas c, float cx, float cy, int gravity, float textAlpha, float backgroundAlpha, float drawableAlpha, DrawableProvider drawableProvider, @PorterDuffColorId int drawableColorId, @Nullable RectF outputDrawRect) { boolean needBackground = BitwiseUtils.hasFlag(flags, FLAG_NEED_BACKGROUND); boolean outlineAffectsBackgroundSize = needBackground && BitwiseUtils.hasFlag(flags, FLAG_OUTLINE_AFFECTS_BACKGROUND_SIZE); if (textAlpha * getVisibility() > 0f || (needBackground && backgroundAlpha * getVisibility() > 0f)) { Drawable drawable = getDrawable(drawableProvider, drawableColorId); - DrawAlgorithms.drawCounter(c, cx, cy, gravity, counter, textSize, textAlpha * getVisibility(), needBackground, outlineAffectsBackgroundSize, Screen.dp(backgroundPadding), this, drawable, drawableGravity, drawableColorId, Screen.dp(drawableMarginDp), backgroundAlpha * getVisibility(), drawableAlpha * getVisibility(), isVisible.getFloatValue()); + DrawAlgorithms.drawCounter(c, cx, cy, gravity, counter, textSize, textAlpha * getVisibility(), needBackground, outlineAffectsBackgroundSize, Screen.dp(backgroundPadding), this, drawable, drawableGravity, drawableColorId, Screen.dp(drawableMarginDp), backgroundAlpha * getVisibility(), drawableAlpha * getVisibility(), isVisible.getFloatValue(), outputDrawRect); } } diff --git a/app/src/main/java/org/thunderdog/challegram/widget/EmojiLayout.java b/app/src/main/java/org/thunderdog/challegram/widget/EmojiLayout.java index 15ec39696a..6d4efcf7e9 100644 --- a/app/src/main/java/org/thunderdog/challegram/widget/EmojiLayout.java +++ b/app/src/main/java/org/thunderdog/challegram/widget/EmojiLayout.java @@ -597,8 +597,8 @@ private void scrollToEmojiSection (int sectionIndex) { } } - public boolean setEmojiStatus (View view, TGStickerObj sticker, int duration) { - return listener != null && listener.onSetEmojiStatus(view, sticker, new TdApi.EmojiStatus(sticker.getCustomEmojiId(), duration)); + public boolean setEmojiStatus (View view, TGStickerObj sticker, long expirationDate) { + return listener != null && listener.onSetEmojiStatus(view, sticker, new TdApi.EmojiStatus(sticker.getCustomEmojiId(), (int) expirationDate)); } public boolean sendSticker (View view, TGStickerObj sticker, TdApi.MessageSendOptions sendOptions) { diff --git a/app/src/main/java/org/thunderdog/challegram/widget/SendButton.java b/app/src/main/java/org/thunderdog/challegram/widget/SendButton.java index 51dffbb076..f856d10871 100644 --- a/app/src/main/java/org/thunderdog/challegram/widget/SendButton.java +++ b/app/src/main/java/org/thunderdog/challegram/widget/SendButton.java @@ -18,37 +18,62 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + +import org.drinkless.tdlib.TdApi; +import org.thunderdog.challegram.R; import org.thunderdog.challegram.config.Config; import org.thunderdog.challegram.core.Lang; +import org.thunderdog.challegram.loader.AvatarReceiver; import org.thunderdog.challegram.navigation.TooltipOverlayView; +import org.thunderdog.challegram.telegram.ChatListener; +import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.telegram.TdlibCache; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.Theme; import org.thunderdog.challegram.theme.ThemeManager; import org.thunderdog.challegram.tool.Drawables; import org.thunderdog.challegram.tool.Paints; +import org.thunderdog.challegram.tool.PorterDuffPaint; import org.thunderdog.challegram.tool.Screen; +import org.thunderdog.challegram.tool.UI; import org.thunderdog.challegram.tool.Views; import org.thunderdog.challegram.util.RateLimiter; +import org.thunderdog.challegram.util.text.Counter; +import org.thunderdog.challegram.util.text.TextColorSet; + +import java.util.concurrent.TimeUnit; import me.vkryl.android.AnimatorUtils; import me.vkryl.android.animator.BoolAnimator; import me.vkryl.android.animator.FactorAnimator; import me.vkryl.core.ColorUtils; +import me.vkryl.core.MathUtils; +import me.vkryl.core.lambda.CancellableRunnable; +import me.vkryl.core.lambda.Destroyable; +import me.vkryl.core.lambda.RunnableInt; +import me.vkryl.td.ChatId; +import me.vkryl.td.Td; public class SendButton extends View implements FactorAnimator.Target, TooltipOverlayView.LocationProvider { private static Paint strokePaint; private final Drawable sendIcon; + private final Drawable sendIconBg; public SendButton (Context context, int sendIconRes) { super(context); + avatarReceiver = new AvatarReceiver(this); sendIcon = Drawables.get(getResources(), sendIconRes); + sendIconBg = Drawables.get(getResources(), sendIconRes); if (strokePaint == null) { strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); strokePaint.setStyle(Paint.Style.STROKE); @@ -108,6 +133,7 @@ protected void onDraw (Canvas c) { if (sendScale > 0f) { if (editFactor != 1f) { final Paint paint = Paints.getSendButtonPaint(); + final Paint paintBg = PorterDuffPaint.get(ColorId.iconLight); final int sourceAlpha = Color.alpha(Theme.chatSendButtonColor()); final boolean saved = editFactor != 0f || sendScale != 1f; if (saved) { @@ -115,6 +141,7 @@ protected void onDraw (Canvas c) { final float scale = Config.DEFAULT_ICON_SWITCH_SCALE + (1f - Config.DEFAULT_ICON_SWITCH_SCALE) * (1f - editFactor) * sendScale; c.scale(scale, scale, cx, cy); paint.setAlpha((int) ((float) sourceAlpha * (1f - editFactor) * sendScale)); + paintBg.setAlpha((int) ((float) sourceAlpha * (1f - editFactor) * sendScale)); } boolean rtl = Lang.rtl(); if (rtl) { @@ -122,9 +149,30 @@ protected void onDraw (Canvas c) { c.save(); c.scale(-1f, 1f, cx, cy); } - Drawables.draw(c, sendIcon, cx - sendIcon.getMinimumWidth() / 2, cy - sendIcon.getMinimumHeight() / 2, paint); + + final int iconW = sendIcon.getMinimumWidth(); + final int iconX = cx - iconW / 2; + final int iconY = cy - sendIcon.getMinimumHeight() / 2; + + final float slowModeDelayProgress = slowModeCounterController != null ? + slowModeCounterController.getSlowModeDelayProgress() : 1f; + + if (slowModeDelayProgress == 1f) { + Drawables.draw(c, sendIcon, iconX, iconY, paint); + } else { + int s = Views.save(c); + c.clipRect(iconW * slowModeDelayProgress + iconX, 0, getMeasuredWidth(), getMeasuredHeight()); + Drawables.draw(c, sendIconBg, iconX, iconY, paintBg); + Views.restore(c, s); + s = Views.save(c); + c.clipRect(0, 0, iconW * slowModeDelayProgress + iconX, getMeasuredHeight()); + Drawables.draw(c, sendIconBg, iconX, iconY, paint); + Views.restore(c, s); + } + if (saved) { paint.setAlpha(sourceAlpha); + paintBg.setAlpha(sourceAlpha); c.restore(); } else if (rtl) { c.restore(); @@ -290,6 +338,10 @@ protected void onDraw (Canvas c) { c.restore(); } } + + if (slowModeCounterController != null) { + slowModeCounterController.draw(c, avatarReceiver, cx, cy, 1f); + } } public void forceState (boolean inEditMode, boolean isActive) { @@ -373,4 +425,312 @@ public void onFactorChangeFinished (int id, float finalFactor, FactorAnimator ca } } } + + private SlowModeCounterController slowModeCounterController; + private final AvatarReceiver avatarReceiver; + private boolean ignoreDrawMessageSender; + + public void setIgnoreDrawMessageSender () { + this.ignoreDrawMessageSender = true; + } + + public void destroySlowModeCounterController () { + if (slowModeCounterController != null) { + slowModeCounterController.performDestroy(); + slowModeCounterController = null; + } + avatarReceiver.destroy(); + } + + @Override + protected void onAttachedToWindow () { + avatarReceiver.attach(); + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow () { + avatarReceiver.detach(); + super.onDetachedFromWindow(); + } + + public SlowModeCounterController getSlowModeCounterController (Tdlib tdlib) { + if (slowModeCounterController != null && slowModeCounterController.tdlib != tdlib) { + destroySlowModeCounterController(); + } + + if (slowModeCounterController == null) { + slowModeCounterController = new SlowModeCounterController(tdlib, this, new TextColorSet() { + @Override + public int defaultTextColor () { + return Theme.getColor(ColorId.textLight); + } + + @Override + public int backgroundColor (boolean isPressed) { + return Theme.getColor(ColorId.filling); + } + }, true, ignoreDrawMessageSender, (a, b, c) -> { + avatarReceiver.requestMessageSender(a, b, c); + invalidate(); + }); + } + return slowModeCounterController; + } + + public static class SlowModeCounterController implements TdlibCache.SupergroupDataChangeListener, ChatListener, Destroyable { + public final Counter counter; + public final RectF lastCounterDrawRect = new RectF(); + private final Tdlib tdlib; + private final boolean needBackground; + private final Drawable anonymousDrawable; + private final View view; + private final boolean ignoreDrawMessageSender; + private final Callback callback; + private float lastVisibilityDraw; + + private long chatId; + private @Nullable TdApi.Chat chat; + + public interface Callback { + void requestMessageSender (@Nullable Tdlib tdlib, @Nullable TdApi.MessageSender sender, @AvatarReceiver.Options int options); + } + + public SlowModeCounterController (Tdlib tdlib, View v, TextColorSet textColorSet, boolean needBackground, boolean ignoreDrawMessageSender, Callback callback) { + this.tdlib = tdlib; + this.view = v; + this.needBackground = needBackground; + this.anonymousDrawable = Drawables.get(v.getResources(), R.drawable.infanf_baseline_incognito_11); + this.ignoreDrawMessageSender = ignoreDrawMessageSender; + this.callback = callback; + + Counter.Builder builder = new Counter.Builder() + .callback((c, s) -> view.invalidate()) + .textSize(11f) + .colorSet(textColorSet); + + if (!needBackground) { + builder.noBackground(); + } + + this.counter = builder.build(); + } + + public Tdlib tdlib () { + return tdlib; + } + + public boolean isVisible () { + return counter.getVisibility() > 0f || hasChatDefaultMessageSenderIdToDraw(); + } + + public void draw (Canvas c, @Nullable AvatarReceiver avatarReceiver, float cx, float cy, float visibility) { + final float cxReal = cx + Screen.dp(5); + final float cyReal = cy + Screen.dp(10f); + + final boolean needScale = visibility != 1f; + int scaleSaveTo = -1; + if (needScale) { + scaleSaveTo = Views.save(c); + c.scale(visibility, visibility, cxReal, cyReal); + } + + lastVisibilityDraw = visibility; + counter.draw(c, cxReal, cyReal, Gravity.CENTER, 1f, lastCounterDrawRect); + + if (!ignoreDrawMessageSender) { + final float sendAsFactor = 1f - counter.getVisibility(); + final long sendAsSender = getChatDefaultMessageSenderId(); + + if (sendAsFactor > 0f && sendAsSender != 0 && sendAsSender != tdlib.myUserId()) { + if (needBackground) { + c.drawCircle(cxReal, cyReal, Screen.dp(9.5f * sendAsFactor), Paints.fillingPaint(counter.backgroundColor(false))); + } + final float radius = Screen.dp(7.5f * sendAsFactor); + + if (sendAsSender == chatId) { + c.drawCircle(cxReal, cyReal, radius, Paints.fillingPaint(Theme.iconLightColor())); + Drawables.draw(c, anonymousDrawable, cxReal - Screen.dp(5.5f), cyReal - Screen.dp(5.5f), PorterDuffPaint.get(ColorId.badgeMutedText)); + } else if (avatarReceiver != null) { + avatarReceiver.setBounds( + (int) (cxReal - radius), + (int) (cyReal - radius), + (int) (cxReal + radius), + (int) (cyReal + radius)); + avatarReceiver.draw(c); + } + } + } + if (needScale) { + Views.restore(c, scaleSaveTo); + } + } + + public void setCurrentChat (long chatId) { + if (this.chatId == chatId) { + return; + } + + stopSlowModeTimerUpdates(); + final long oldChatId = this.chatId; + this.chatId = chatId; + this.chat = tdlib.chat(chatId); + + if (oldChatId != 0) { + final long supergroupId = ChatId.toSupergroupId(oldChatId); + if (supergroupId != 0) { + tdlib.cache().unsubscribeFromSupergroupUpdates(supergroupId, this); + } + tdlib.listeners().unsubscribeFromChatUpdates(chatId, this); + } + + if (chatId != 0) { + final long supergroupId = ChatId.toSupergroupId(chatId); + if (supergroupId != 0) { + tdlib.cache().subscribeToSupergroupUpdates(supergroupId, this); + } + tdlib.listeners().subscribeToChatUpdates(chatId, this); + } + + updateChatDefaultMessageSenderId(chat != null ? chat.messageSenderId : null); + updateSlowModeTimer(false); + } + + public float getSlowModeDelayProgress () { + return slowModeDelayProgress; + } + + public void updateSlowModeTimer (boolean animated) { + if (!tdlib.isSupergroup(chatId)) { + setSlowModeTimer(0, 0, animated); + return; + } + + final TdApi.SupergroupFullInfo info = tdlib.cache().supergroupFull(ChatId.toSupergroupId(chatId), false); + if (info == null) { + tdlib.cache().supergroupFull(ChatId.toSupergroupId(chatId)); + setSlowModeTimer(0, 0, animated); + return; + } + + final long slowModeDelayExpiresIn = tdlib.cache().getSlowModeDelayExpiresIn(ChatId.toSupergroupId(chatId), TimeUnit.SECONDS); + setSlowModeTimer(slowModeDelayExpiresIn, info.slowModeDelay, animated); + + if (slowModeDelayExpiresIn > 0) { + startSlowModeTimerUpdates(); + } + } + + private CancellableRunnable slowModeTimerUpdateRunnable; + + private void startSlowModeTimerUpdates () { + stopSlowModeTimerUpdates(); + UI.post(slowModeTimerUpdateRunnable = new CancellableRunnable() { + @Override + public void act () { + updateSlowModeTimer(true); + } + }, 500); + } + + private void stopSlowModeTimerUpdates () { + if (slowModeTimerUpdateRunnable != null) { + slowModeTimerUpdateRunnable.cancel(); + slowModeTimerUpdateRunnable = null; + } + } + + private RunnableInt slowModeCounterUpdateListener; + + public void setSlowModeCounterUpdateListener (RunnableInt slowModeCounterUpdateListener) { + this.slowModeCounterUpdateListener = slowModeCounterUpdateListener; + } + + private float slowModeDelayProgress = 1f; + + private void setSlowModeTimer (long seconds, long slowModeDelaySeconds, boolean animated) { + this.slowModeDelayProgress = slowModeDelaySeconds == 0 ? 1f: + ((float) Math.max(slowModeDelaySeconds - seconds, 0)) / slowModeDelaySeconds; + + this.counter.setCount(seconds, false, formatElapsedTime((int) seconds), animated); + this.view.invalidate(); + if (slowModeCounterUpdateListener != null) { + slowModeCounterUpdateListener.runWithInt((int) seconds); + } + } + + public static String formatElapsedTime (int seconds) { + final int minutes = seconds / 60; + if (minutes > 0) { + return Lang.plural(R.string.SlowModeMinutesShort, minutes); + } else { + return Integer.toString(seconds); + } + } + + @Override + public void onSupergroupFullUpdated (long supergroupId, TdApi.SupergroupFullInfo newSupergroupFull) { + UI.post(() -> { + if (supergroupId == ChatId.toSupergroupId(chatId)) { + updateSlowModeTimer(true); + } + }); + } + + /* Default Sender Id */ + + @Override + public void onChatDefaultMessageSenderIdChanged (long chatId, TdApi.MessageSender senderId) { + UI.post(() -> { + if (chatId == this.chatId) { + updateChatDefaultMessageSenderId(senderId); + } + }); + } + + private void updateChatDefaultMessageSenderId (TdApi.MessageSender sender) { + final boolean isUserSender = Td.getSenderId(sender) == tdlib.myUserId(); + final boolean isGroupSender = Td.getSenderId(sender) == chatId; + updateChatDefaultMessageSenderId(sender, isUserSender, isGroupSender); + } + + private void updateChatDefaultMessageSenderId (TdApi.MessageSender sender, boolean isPersonal, boolean isAnonymous) { + callback.requestMessageSender(tdlib, sender, AvatarReceiver.Options.NONE); + } + + private long getChatDefaultMessageSenderId () { + return chat != null && chat.messageSenderId != null ? Td.getSenderId(chat.messageSenderId) : 0; + } + + private boolean hasChatDefaultMessageSenderIdToDraw () { + return chat != null && chat.messageSenderId != null && Td.getSenderId(chat.messageSenderId) != tdlib.myUserId(); + } + + + + /* * */ + + private final RectF tmpRectF = new RectF(); + + public void buildClipPath (View v, Path clipPath) { + final boolean hasSenderId = hasChatDefaultMessageSenderIdToDraw(); + final float cx = v.getMeasuredWidth() / 2f + Screen.dp(5); + final float cy = v.getMeasuredHeight() / 2f + Screen.dp(10f); + final float width = MathUtils.fromTo(hasSenderId ? Screen.dp(19) : 0, lastCounterDrawRect.width(), counter.getVisibility()) * lastVisibilityDraw; + final float height = MathUtils.fromTo(hasSenderId ? Screen.dp(19) : 0, lastCounterDrawRect.height(), counter.getVisibility()) * lastVisibilityDraw; + final float radius = Math.min(width, height) / 2f; + + tmpRectF.set(cx - width / 2f, cy - height / 2f, cx + width / 2f, cy + height / 2f); + + clipPath.reset(); + clipPath.addRect(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight(), Path.Direction.CW); + clipPath.addRoundRect(tmpRectF, radius, radius, Path.Direction.CCW); + clipPath.close(); + } + + @Override + public void performDestroy () { + setCurrentChat(0); + } + } } diff --git a/app/src/main/res/drawable/baseline_block_18.xml b/app/src/main/res/drawable/baseline_block_18.xml new file mode 100755 index 0000000000..214af017a5 --- /dev/null +++ b/app/src/main/res/drawable/baseline_block_18.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_format_list_bulleted_type_24.xml b/app/src/main/res/drawable/baseline_format_list_bulleted_type_24.xml new file mode 100644 index 0000000000..324dc54a1e --- /dev/null +++ b/app/src/main/res/drawable/baseline_format_list_bulleted_type_24.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_info_14.xml b/app/src/main/res/drawable/baseline_info_14.xml new file mode 100755 index 0000000000..f3d1b1f9d9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_info_14.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_video_chat_24.xml b/app/src/main/res/drawable/baseline_video_chat_24.xml new file mode 100644 index 0000000000..19f2cb4c52 --- /dev/null +++ b/app/src/main/res/drawable/baseline_video_chat_24.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_warning_14.xml b/app/src/main/res/drawable/baseline_warning_14.xml new file mode 100755 index 0000000000..d723a396d8 --- /dev/null +++ b/app/src/main/res/drawable/baseline_warning_14.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/dot_baseline_acc_personal_18.xml b/app/src/main/res/drawable/dot_baseline_acc_personal_18.xml new file mode 100644 index 0000000000..bc98d68bde --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_acc_personal_18.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_account_circle_18.xml b/app/src/main/res/drawable/dot_baseline_account_circle_18.xml new file mode 100644 index 0000000000..2dddcd0dc3 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_account_circle_18.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_channel_accept_24.xml b/app/src/main/res/drawable/dot_baseline_channel_accept_24.xml new file mode 100644 index 0000000000..51ec7af9fe --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_channel_accept_24.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_channel_circle_18.xml b/app/src/main/res/drawable/dot_baseline_channel_circle_18.xml new file mode 100644 index 0000000000..63f1c0c3ed --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_channel_circle_18.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_flip_horizontal_24.xml b/app/src/main/res/drawable/dot_baseline_flip_horizontal_24.xml new file mode 100644 index 0000000000..d80baa6e57 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_flip_horizontal_24.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_group_accept_24.xml b/app/src/main/res/drawable/dot_baseline_group_accept_24.xml new file mode 100644 index 0000000000..b6e4928fe2 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_group_accept_24.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_group_circle_18.xml b/app/src/main/res/drawable/dot_baseline_group_circle_18.xml new file mode 100644 index 0000000000..8a95b05507 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_group_circle_18.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_image_check_24.xml b/app/src/main/res/drawable/dot_baseline_image_check_24.xml new file mode 100644 index 0000000000..62a29abb15 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_image_check_24.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_baseline_profile_accept_24.xml b/app/src/main/res/drawable/dot_baseline_profile_accept_24.xml new file mode 100644 index 0000000000..956e89a2c5 --- /dev/null +++ b/app/src/main/res/drawable/dot_baseline_profile_accept_24.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 64adc8e350..fe725e01a7 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -413,6 +413,7 @@ + @@ -493,6 +494,8 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c3c81d13d..0d943d600f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -178,6 +178,11 @@ %1$s week %1$s weeks + Slow mode is active. You can send the next message in %1$s second + Slow mode is active. You can send the next message in %1$s seconds + Slow mode is active. You can send the next message in %1$s minute + Slow mode is active. You can send the next message in %1$s minutes + in %1$s hour in %1$s hours %1$s chat @@ -282,6 +287,8 @@ Messages Select Chat Message + Text is disabled + Admins of this group disabled ability to send text messages. Reply Comment as %1$s @@ -580,6 +587,16 @@ Gallery Camera + Set Profile Photo + Set Group Photo + Set Channel Photo + Set Public Photo + Remove Profile Photo + Remove Group Photo + Remove Channel Photo + Remove Public Photo + Delete current photo? + All message types Text messages Photos @@ -1273,6 +1290,10 @@ Today Yesterday Public group + Public Photo + Photo set + No Photo set + Public Photo will be visible for everyone who is restricted from seeing your regular profile photo. Group Rename contact Themes @@ -1811,6 +1832,8 @@ Manage video chats Manage live streams Manage topics + Stories + Messages Post Stories Edit Stories of Others Delete Stories of Others @@ -3431,6 +3454,8 @@ %1$s (%2$s)\n%3$s %1$s\n\n%2$s + Allowed %1$s/%2$s + Allowed %1$s/%2$s %1$s of %2$s %1$s of %2$s %1$s of %2$s @@ -4254,6 +4279,8 @@ Members will be able to send only one message every %1$s hour. Members will be able to send only one message every %1$s hours. Off + %1$sm + %1$sm Invite Links Primary Invite Link @@ -4687,6 +4714,18 @@ watched %1$s hour ago watched %1$s hours ago + Edited just now + Edited today at %1$s + Edited yesterday at %1$s + Edited on %1$s at %2$s + Edited on %1$s at %2$s + Edited %1$s second ago + Edited %1$s seconds ago + Edited %1$s minute ago + Edited %1$s minutes ago + Edited %1$s hour ago + Edited %1$s hours ago + reacted just now reacted today at %1$s reacted yesterday at %1$s @@ -4747,7 +4786,7 @@ Set until today at %1$s Set until tomorrow at %1$s Set until %1$s at %2$s - + Text formatting Tools Translate @@ -4820,7 +4859,7 @@ %1$s more %1$s more - + Chat types Chat Folders Create folders for different groups of chats and quickly switch between them. @@ -4909,6 +4948,10 @@ This message is a reply to the message that was deleted. This message is from another chat. Tap again to view. + Swipe to choose specific link preview + This is a service message from Telegram + This message is unsupported in the installed version of Telegram X. + From f4f3a75c4e99280985da77aa2e85f77b9a71445f Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:56:50 +0900 Subject: [PATCH 10/23] Fixed URL in the invite text --- .../java/org/thunderdog/challegram/telegram/TdlibCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java index 488448bb58..1b44817e2e 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibCache.java @@ -268,13 +268,13 @@ public void onUiStateChanged (int newState) { public void getInviteText (@Nullable final RunnableData callback) { getDownloadUrl(httpUrl -> { if (callback != null) { - String text = Lang.getString(R.string.InviteText, BuildConfig.PROJECT_NAME, httpUrl); + String text = Lang.getString(R.string.InviteText, BuildConfig.PROJECT_NAME, httpUrl.url); callback.runWithData(new TdApi.Text(text)); } }); } - private AppInstallationUtil.DownloadUrl toDownloadUrl (@Nullable TdApi.HttpUrl url) { + private @NonNull AppInstallationUtil.DownloadUrl toDownloadUrl (@Nullable TdApi.HttpUrl url) { if (url != null && tdlib.hasUrgentInAppUpdate()) { return new AppInstallationUtil.DownloadUrl(AppInstallationUtil.InstallerId.UNKNOWN, url.url); } From 2d5f3940f518930beedfb38edd46a1d319980cbf Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:15:48 +0900 Subject: [PATCH 11/23] Declare notifications support for `Android Auto` --- app/src/main/AndroidManifest.xml | 6 +++++- app/src/main/res/xml/automotive_app_desc.xml | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/xml/automotive_app_desc.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea8db76ba8..d2ee541c9e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -456,6 +456,10 @@ + android:value="@bool/com_samsung_android_icon_container_has_icon_container" /> + + \ No newline at end of file diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml new file mode 100644 index 0000000000..ec76c3d5fb --- /dev/null +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 6ced66136c66bcce18aadfa88a1a83712593b884 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:08:58 +0900 Subject: [PATCH 12/23] Fixed interaction buttons being available for fake messages and bot description --- app/src/main/java/org/thunderdog/challegram/data/TGMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a1c0b27243..e2b77f2af3 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java @@ -5617,7 +5617,7 @@ public boolean onMessageSendAcknowledged (long messageId) { } public boolean allowInteraction () { - return !isEventLog() && !isThreadHeader(); + return !isFakeMessage() && !isEventLog() && !isThreadHeader(); } public boolean canReplyTo () { From de1ec7035b3f97ffac79733e6631dc32e4380638 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 00:17:26 +0400 Subject: [PATCH 13/23] In-app ability to permanently delete account It was always possible to do via `my.telegram.org`, however, it has to be provided in the app for compliance with updated store policies. --- .../challegram/navigation/ViewController.java | 11 + .../thunderdog/challegram/telegram/Tdlib.java | 8 +- .../challegram/telegram/TdlibUi.java | 196 ++++++++++++------ .../challegram/ui/EditBaseController.java | 2 +- .../ui/EditDeleteAccountReasonController.java | 117 +++++++++++ .../challegram/ui/EditLanguageController.java | 2 +- .../challegram/ui/EditTextController.java | 161 ++++++++++++++ .../challegram/ui/PasswordController.java | 47 ++++- .../ui/SettingsLogOutController.java | 57 ++++- .../ui/SettingsPrivacyController.java | 7 + .../res/drawable/baseline_delete_alert_24.xml | 8 + .../main/res/drawable/baseline_logout_24.xml | 8 + app/src/main/res/values/ids.xml | 2 + app/src/main/res/values/strings.xml | 21 +- 14 files changed, 574 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/org/thunderdog/challegram/ui/EditDeleteAccountReasonController.java create mode 100644 app/src/main/java/org/thunderdog/challegram/ui/EditTextController.java create mode 100644 app/src/main/res/drawable/baseline_delete_alert_24.xml create mode 100644 app/src/main/res/drawable/baseline_logout_24.xml diff --git a/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java b/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java index 64d8987ea6..ecb42afe3e 100644 --- a/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java +++ b/app/src/main/java/org/thunderdog/challegram/navigation/ViewController.java @@ -450,6 +450,17 @@ public final ViewController destroyStackItemAt (int index) { return navigationController != null ? navigationController.getStack().destroy(index) : null; } + public final ViewController destroyPreviousStackItem () { + if (navigationController != null) { + NavigationStack stack = navigationController.getStack(); + int currentIndex = stack.size() - 1; + if (currentIndex > 0) { + return stack.destroy(currentIndex - 1); + } + } + return null; + } + public final ViewController removeStackItemById (int id) { return navigationController != null ? navigationController.getStack().removeById(id) : null; } diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java index 4b764b00bd..65ea839773 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java @@ -1157,13 +1157,19 @@ public TdApi.AuthorizationState authorizationState () { return authorizationState; } - public void signOut () { + public boolean switchToNextAuthorizedAccount () { if (context().preferredAccountId() == accountId) { int nextAccountId = context().findNextAccountId(accountId); if (nextAccountId != TdlibAccount.NO_ID) { context().changePreferredAccountId(nextAccountId, TdlibManager.SWITCH_REASON_UNAUTHORIZED); + return true; } } + return false; + } + + public void signOut () { + switchToNextAuthorizedAccount(); boolean isMulti = context().isMultiUser(); String name = isMulti ? TD.getUserName(account().getFirstName(), account().getLastName()) : null; incrementReferenceCount(REFERENCE_TYPE_JOB); diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java index d1d086239e..018b94b5b7 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java @@ -106,6 +106,7 @@ import org.thunderdog.challegram.ui.ChatsController; import org.thunderdog.challegram.ui.EditChatFolderController; import org.thunderdog.challegram.ui.EditChatLinkController; +import org.thunderdog.challegram.ui.EditDeleteAccountReasonController; import org.thunderdog.challegram.ui.EditNameController; import org.thunderdog.challegram.ui.EditProxyController; import org.thunderdog.challegram.ui.EditRightsController; @@ -3728,6 +3729,69 @@ public void openProxyAlert (TdlibDelegate context, String server, int port, TdAp }); } + // Delete account on server + + public void permanentlyDeleteAccount (ViewController context, boolean showAlternatives) { + boolean needShowAlternatives = tdlib.isAuthorized() && showAlternatives; + context.showOptions( + Lang.getMarkdownString(context, needShowAlternatives ? R.string.DeleteAccountConfirmFirst : R.string.DeleteAccountConfirm), + new int[] {R.id.btn_deleteAccount, R.id.btn_cancel}, + new String[] {Lang.getString(needShowAlternatives ? R.string.DeleteAccountConfirmFirstBtn : R.string.DeleteAccountConfirmBtn), Lang.getString(R.string.Cancel)}, + new int[] {ViewController.OPTION_COLOR_RED, ViewController.OPTION_COLOR_NORMAL}, + new int[] {R.drawable.baseline_delete_alert_24, R.drawable.baseline_cancel_24}, + (optionItemView, id) -> { + if (id == R.id.btn_deleteAccount) { + if (needShowAlternatives) { + SettingsLogOutController c = new SettingsLogOutController(context.context(), tdlib); + c.setArguments(SettingsLogOutController.Type.DELETE_ACCOUNT); + context.navigateTo(c); + return true; + } + + tdlib.send(new TdApi.GetPasswordState(), (passwordState, error) -> context.runOnUiThreadOptional(() -> { + if (error != null) { + UI.showError(error); + return; + } + context.runOnUiThreadOptional(() -> { + if (!passwordState.hasPassword) { + context.navigateTo(new EditDeleteAccountReasonController(context.context(), tdlib)); + return; + } + promptPassword(context, passwordState, new PasswordController.CustomConfirmDelegate() { + @Override + public CharSequence getName () { + return Lang.getString(R.string.DeleteAccount); + } + + @Override + public boolean needNext () { + return true; + } + + @Override + public void onPasswordConfirmed (ViewController c, String password) { + EditDeleteAccountReasonController target = new EditDeleteAccountReasonController(context.context(), tdlib); + target.setArguments(password); + context.navigateTo(target); + } + }); + }); + })); + } + return true; + } + ); + } + + private void promptPassword (ViewController context, TdApi.PasswordState passwordState, @NonNull PasswordController.CustomConfirmDelegate confirmDelegate) { + PasswordController controller = new PasswordController(context.context(), context.tdlib()); + controller.setArguments(new PasswordController.Args(PasswordController.MODE_CUSTOM_CONFIRM, passwordState) + .setConfirmDelegate(confirmDelegate) + ); + context.navigateTo(controller); + } + // Log out public void logOut (ViewController context, boolean showAlternatives) { @@ -5396,7 +5460,7 @@ public static void removeAccount (ViewController context, final TdlibAccount } private static void removeAccount (ViewController context, final TdlibAccount account, boolean isSignOut) { - context.showOptions(Lang.getStringBold(isSignOut ? R.string.SignOutHint2 : R.string.RemoveAccountHint2, account.getName()), new int[]{R.id.btn_removeAccount, R.id.btn_cancel}, new String[]{Lang.getString(R.string.LogOut), Lang.getString(R.string.Cancel)}, new int[]{ViewController.OPTION_COLOR_RED, ViewController.OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_delete_forever_24, R.drawable.baseline_cancel_24}, (itemView, id) -> { + context.showOptions(Lang.getStringBold(isSignOut ? R.string.SignOutHint2 : R.string.RemoveAccountHint2, account.getName()), new int[]{R.id.btn_removeAccount, R.id.btn_cancel}, new String[]{Lang.getString(R.string.LogOut), Lang.getString(R.string.Cancel)}, new int[]{ViewController.OPTION_COLOR_RED, ViewController.OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_logout_24, R.drawable.baseline_cancel_24}, (itemView, id) -> { if (id == R.id.btn_removeAccount) { account.tdlib().signOut(); } @@ -6368,68 +6432,80 @@ default void onOwnershipTransferAbilityChecked (TdApi.Object result) { } } public void requestTransferOwnership (ViewController context, CharSequence finalAlertMessageText, OwnershipTransferListener listener) { - tdlib.client().send(new TdApi.CanTransferOwnership(), result -> { - tdlib.ui().post(() -> { - listener.onOwnershipTransferAbilityChecked(result); - switch (result.getConstructor()) { - case TdApi.CanTransferOwnershipResultOk.CONSTRUCTOR: - tdlib.client().send(new TdApi.GetPasswordState(), state -> { - if (state.getConstructor() != TdApi.PasswordState.CONSTRUCTOR) return; - post(() -> { - PasswordController controller = new PasswordController(context.context(), context.tdlib()); - controller.setArguments(new PasswordController.Args(PasswordController.MODE_TRANSFER_OWNERSHIP_CONFIRM, (TdApi.PasswordState) state).setSuccessListener(password -> { - // Ask if the user REALLY wants to transfer ownership, because this operation is serious - context.addOneShotFocusListener(() -> - context.showOptions(new ViewController.Options.Builder() - .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipAlert), finalAlertMessageText)) - .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.TransferOwnershipConfirm), ViewController.OPTION_COLOR_RED, R.drawable.templarian_baseline_account_switch_24)) - .cancelItem() - .build(), (optionView, id) -> { - if (id == R.id.btn_next) { - listener.onOwnershipTransferConfirmed(password); - } - return true; - }) - ); - })); - context.navigateTo(controller); - }); + tdlib.send(new TdApi.CanTransferOwnership(), (canTransferOwnership, error) -> post(() -> { + listener.onOwnershipTransferAbilityChecked(canTransferOwnership != null ? canTransferOwnership : error); + if (error != null) { + UI.showError(error); + return; + } + switch (canTransferOwnership.getConstructor()) { + case TdApi.CanTransferOwnershipResultOk.CONSTRUCTOR: { + tdlib.send(new TdApi.GetPasswordState(), (passwordState, error1) -> { + if (error1 != null) { + UI.showError(error1); + return; + } + post(() -> { + PasswordController controller = new PasswordController(context.context(), context.tdlib()); + controller.setArguments(new PasswordController.Args(PasswordController.MODE_TRANSFER_OWNERSHIP_CONFIRM, passwordState).setSuccessListener(password -> { + // Ask if the user REALLY wants to transfer ownership, because this operation is serious + context.addOneShotFocusListener(() -> + context.showOptions(new ViewController.Options.Builder() + .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipAlert), finalAlertMessageText)) + .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.TransferOwnershipConfirm), ViewController.OPTION_COLOR_RED, R.drawable.templarian_baseline_account_switch_24)) + .cancelItem() + .build(), (optionView, id) -> { + if (id == R.id.btn_next) { + listener.onOwnershipTransferConfirmed(password); + } + return true; + }) + ); + })); + context.navigateTo(controller); }); - break; - case TdApi.CanTransferOwnershipResultPasswordNeeded.CONSTRUCTOR: - context.showOptions(new ViewController.Options.Builder() - .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityPasswordNeeded))) - .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.TransferOwnershipSecurityActionSetPassword), ViewController.OPTION_COLOR_BLUE, R.drawable.mrgrigri_baseline_textbox_password_24)) - .cancelItem() - .build(), (optionView, id) -> { - if (id == R.id.btn_next) { - Settings2FAController controller = new Settings2FAController(context.context(), context.tdlib()); - controller.setArguments(new Settings2FAController.Args(null)); - context.navigateTo(controller); - } - return true; + }); + break; + } + case TdApi.CanTransferOwnershipResultPasswordNeeded.CONSTRUCTOR: { + context.showOptions(new ViewController.Options.Builder() + .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityPasswordNeeded))) + .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.TransferOwnershipSecurityActionSetPassword), ViewController.OPTION_COLOR_BLUE, R.drawable.mrgrigri_baseline_textbox_password_24)) + .cancelItem() + .build(), (optionView, id) -> { + if (id == R.id.btn_next) { + Settings2FAController controller = new Settings2FAController(context.context(), context.tdlib()); + controller.setArguments(new Settings2FAController.Args(null)); + context.navigateTo(controller); } - ); - break; - case TdApi.CanTransferOwnershipResultPasswordTooFresh.CONSTRUCTOR: - context.showOptions(new ViewController.Options.Builder() - .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityWaitPassword, Lang.getDuration(((TdApi.CanTransferOwnershipResultPasswordTooFresh) result).retryAfter)))) - .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.OK), ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_check_circle_24)) - .cancelItem() - .build(), (optionView, id) -> true - ); - break; - case TdApi.CanTransferOwnershipResultSessionTooFresh.CONSTRUCTOR: - context.showOptions(new ViewController.Options.Builder() - .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityWaitSession, Lang.getDuration(((TdApi.CanTransferOwnershipResultSessionTooFresh) result).retryAfter)))) - .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.OK), ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_check_circle_24)) - .cancelItem() - .build(), (optionView, id) -> true - ); - break; + return true; + } + ); + break; } - }); - }); + case TdApi.CanTransferOwnershipResultPasswordTooFresh.CONSTRUCTOR: { + context.showOptions(new ViewController.Options.Builder() + .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityWaitPassword, Lang.getDuration(((TdApi.CanTransferOwnershipResultPasswordTooFresh) canTransferOwnership).retryAfter)))) + .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.OK), ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_check_circle_24)) + .cancelItem() + .build(), (optionView, id) -> true + ); + break; + } + case TdApi.CanTransferOwnershipResultSessionTooFresh.CONSTRUCTOR: { + context.showOptions(new ViewController.Options.Builder() + .info(Strings.getTitleAndText(Lang.getString(R.string.TransferOwnershipSecurityAlert), Lang.getMarkdownString(context, R.string.TransferOwnershipSecurityWaitSession, Lang.getDuration(((TdApi.CanTransferOwnershipResultSessionTooFresh) canTransferOwnership).retryAfter)))) + .item(new ViewController.OptionItem(R.id.btn_next, Lang.getString(R.string.OK), ViewController.OPTION_COLOR_NORMAL, R.drawable.baseline_check_circle_24)) + .cancelItem() + .build(), (optionView, id) -> true + ); + break; + } + default: + Td.assertCanTransferOwnershipResult_ac091006(); + throw Td.unsupported(canTransferOwnership); + } + })); } public void saveGifs (List downloadedFiles) { diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditBaseController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditBaseController.java index 5eac5f5840..520f7b85eb 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EditBaseController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditBaseController.java @@ -115,7 +115,7 @@ public int getRootColorId () { } @Override - public boolean onDoneClick (View v) { + public final boolean onDoneClick (View v) { return onDoneClick(); } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditDeleteAccountReasonController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditDeleteAccountReasonController.java new file mode 100644 index 0000000000..f5802f5703 --- /dev/null +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditDeleteAccountReasonController.java @@ -0,0 +1,117 @@ +/* + * This file is a part of Telegram X + * Copyright © 2014 (tgx-android@pm.me) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * File created on 25/12/2023 at 00:17 + */ +package org.thunderdog.challegram.ui; + +import android.content.Context; + +import org.drinkless.tdlib.TdApi; +import org.thunderdog.challegram.R; +import org.thunderdog.challegram.core.Lang; +import org.thunderdog.challegram.data.TD; +import org.thunderdog.challegram.navigation.ViewController; +import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.telegram.TdlibAccount; +import org.thunderdog.challegram.tool.Strings; +import org.thunderdog.challegram.widget.DoneButton; + +import me.vkryl.core.StringUtils; + +public class EditDeleteAccountReasonController extends EditTextController { + public EditDeleteAccountReasonController (Context context, Tdlib tdlib) { + super(context, tdlib); + setDelegate(new Delegate() { + @Override + public int getId () { + return R.id.controller_deleteAccount; + } + + @Override + public int getDoneIcon () { + return R.drawable.baseline_delete_alert_24; + } + + @Override + public CharSequence getName () { + return Lang.getString(R.string.DeleteAccount); + } + + @Override + public CharSequence getHint () { + return Lang.getString(R.string.DeleteAccountReason); + } + + @Override + public CharSequence getDescription () { + return Lang.getMarkdownString(EditDeleteAccountReasonController.this, R.string.DeleteAccountDescription); + } + + @Override + public boolean allowEmptyValue () { + return false; + } + + @Override + public boolean needFocusInput () { + return false; + } + + @Override + public boolean onDonePressed (EditTextController controller, DoneButton button, String value) { + if (isInProgress()) + return false; + if (!StringUtils.isEmptyOrBlank(value)) { + showOptions( + Lang.getMarkdownString(controller, R.string.DeleteAccountConfirmFinal), + new int[] {R.id.btn_deleteAccount, R.id.btn_cancel}, + new String[] {Lang.getString(R.string.DeleteAccountConfirmFinalBtn), Lang.getString(R.string.Cancel)}, + new int[] {OPTION_COLOR_RED, OPTION_COLOR_NORMAL}, + new int[] {R.drawable.baseline_delete_alert_24, R.drawable.baseline_cancel_24}, + (optionItemView, id) -> { + if (id == R.id.btn_deleteAccount) { + TdlibAccount account = tdlib.account(); + String name = account.getName(); + String phoneNumber = account.getPhoneNumber(); + setDoneInProgress(true); + setStackLocked(true); + tdlib.send(new TdApi.DeleteAccount(value, getArguments()), (ok, error) -> runOnUiThreadOptional(() -> { + setStackLocked(false); + setDoneInProgress(false); + if (error != null) { + context().tooltipManager() + .builder(getDoneButton()) + .icon(R.drawable.baseline_warning_24) + .show(tdlib, TD.toErrorString(error)); + } else { + tdlib.switchToNextAuthorizedAccount(); + openAlert(R.string.AccountDeleted, Lang.getMarkdownString(controller, R.string.AccountDeletedText, name, Strings.formatPhone(phoneNumber))); + } + })); + } + return true; + } + ); + return true; + } + return false; + } + }); + addOneShotFocusListener(() -> { + ViewController c = navigationController().getStack().getPrevious(); + if (c instanceof PasswordController) { // Password confirmation + destroyPreviousStackItem(); + } + }); + } +} diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditLanguageController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditLanguageController.java index dc59042eb0..25466bf253 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/EditLanguageController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditLanguageController.java @@ -403,7 +403,7 @@ private static void copyValue (TdApi.LanguagePackStringValuePluralized to, TdApi } @Override - public boolean onDoneClick (View v) { + public final boolean onDoneClick () { if (saveString()) { if (exitOnSave) { onSaveCompleted(); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/EditTextController.java b/app/src/main/java/org/thunderdog/challegram/ui/EditTextController.java new file mode 100644 index 0000000000..eecc280d45 --- /dev/null +++ b/app/src/main/java/org/thunderdog/challegram/ui/EditTextController.java @@ -0,0 +1,161 @@ +/* + * This file is a part of Telegram X + * Copyright © 2014 (tgx-android@pm.me) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * File created on 24/12/2023 at 22:32 + */ +package org.thunderdog.challegram.ui; + +import android.content.Context; +import android.text.InputFilter; +import android.text.InputType; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.DrawableRes; +import androidx.annotation.IdRes; +import androidx.recyclerview.widget.RecyclerView; + +import org.thunderdog.challegram.R; +import org.thunderdog.challegram.emoji.EmojiFilter; +import org.thunderdog.challegram.telegram.Tdlib; +import org.thunderdog.challegram.theme.ColorId; +import org.thunderdog.challegram.tool.Views; +import org.thunderdog.challegram.util.CharacterStyleFilter; +import org.thunderdog.challegram.widget.DoneButton; +import org.thunderdog.challegram.widget.MaterialEditTextGroup; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import me.vkryl.android.text.CodePointCountFilter; +import me.vkryl.android.widget.FrameLayoutFix; +import me.vkryl.core.StringUtils; + +public class EditTextController extends EditBaseController { + public interface Delegate { + @IdRes int getId (); + default @DrawableRes int getDoneIcon () { + return 0; + } + CharSequence getName (); + CharSequence getHint (); + CharSequence getDescription (); + default String getCurrentValue () { + return null; + } + default void onValueChanged (EditTextController controller, String value) { + controller.setDoneVisible(allowEmptyValue() || !StringUtils.isEmptyOrBlank(value)); + } + boolean onDonePressed (EditTextController controller, DoneButton button, String value); + default int getMaxLength () { + return 0; + } + default boolean allowEmptyValue () { + return true; + } + default boolean needFocusInput () { + return true; + } + } + + private Delegate delegate; + + public final void setDelegate (Delegate delegate) { + this.delegate = delegate; + } + + public EditTextController (Context context, Tdlib tdlib) { + super(context, tdlib); + } + + @Override + public int getId () { + return delegate.getId(); + } + + @Override + public CharSequence getName () { + return delegate.getName(); + } + + private SettingsAdapter adapter; + private String currentValue; + + @Override + protected void onCreateView (Context context, FrameLayoutFix contentView, RecyclerView recyclerView) { + final int maxLength = delegate.getMaxLength(); + adapter = new SettingsAdapter(this) { + @Override + protected void modifyEditText (ListItem item, ViewGroup parent, MaterialEditTextGroup editText) { + editText.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + Views.setSingleLine(editText.getEditText(), false); + if (maxLength > 0) { + editText.setMaxLength(maxLength); + } + } + }; + + currentValue = delegate.getCurrentValue(); + CharSequence hint = delegate.getHint(); + ListItem item = new ListItem(maxLength > 0 ? ListItem.TYPE_EDITTEXT_COUNTERED : ListItem.TYPE_EDITTEXT, R.id.input, 0, hint, false); + if (!StringUtils.isEmpty(currentValue)) { + item.setStringValue(currentValue); + } + + List inputFilters = new ArrayList<>(); + if (maxLength > 0) { + inputFilters.add(new CodePointCountFilter(maxLength)); + } + Collections.addAll(inputFilters, + new EmojiFilter(), + new CharacterStyleFilter() + ); + item.setInputFilters(inputFilters.toArray(new InputFilter[0])); + + List items = new ArrayList<>(); + items.add(item); + + CharSequence description = delegate.getDescription(); + if (!StringUtils.isEmpty(description)) { + ListItem descriptionItem = new ListItem(ListItem.TYPE_DESCRIPTION, R.id.description, 0, description, false) + .setTextColorId(ColorId.textLight); + items.add(descriptionItem); + } + + adapter.setTextChangeListener((id, item1, v, text) -> { + currentValue = text; + delegate.onValueChanged(this, text); + }); + adapter.setLockFocusOn(this, delegate.needFocusInput()); + adapter.setItems(items, false); + + recyclerView.setAdapter(adapter); + recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); + + setDoneVisible(delegate.allowEmptyValue()); + @DrawableRes int iconRes = delegate.getDoneIcon(); + if (iconRes != 0) { + setDoneIcon(iconRes); + } + } + + @Override + protected void onProgressStateChanged (boolean inProgress) { + adapter.updateLockEditTextById(R.id.input, inProgress ? currentValue : null); + } + + @Override + protected boolean onDoneClick () { + return delegate.onDonePressed(this, getDoneButton(), currentValue); + } +} diff --git a/app/src/main/java/org/thunderdog/challegram/ui/PasswordController.java b/app/src/main/java/org/thunderdog/challegram/ui/PasswordController.java index 2bcb05db90..8593cd4a9d 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/PasswordController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/PasswordController.java @@ -91,6 +91,7 @@ public class PasswordController extends ViewController public static final int MODE_CONFIRM = 11; public static final int MODE_CODE_EMAIL = 12; public static final int MODE_EMAIL_LOGIN = 13; + public static final int MODE_CUSTOM_CONFIRM = 14; private final Handler handler = new Handler(this); @@ -99,6 +100,7 @@ public static class Args { public final TdApi.PasswordState state; public final TdApi.AuthorizationState authState; public @Nullable String phoneNumber; + public @Nullable CustomConfirmDelegate confirmDelegate; public Args (int mode, TdApi.PasswordState state) { this.mode = mode; @@ -174,6 +176,11 @@ public Args setSuccessListener (@Nullable RunnableData onSuccessListener this.onSuccessListener = onSuccessListener; return this; } + + public Args setConfirmDelegate (@Nullable CustomConfirmDelegate confirmDelegate) { + this.confirmDelegate = confirmDelegate; + return this; + } } private int mode; @@ -206,8 +213,19 @@ public void setArguments (Args args) { this.state = args.state; this.authState = args.authState; this.formattedPhone = args.phoneNumber; + this.confirmDelegate = args.confirmDelegate; } + public interface CustomConfirmDelegate { + default CharSequence getName () { + return Lang.getString(R.string.EnterPassword); + } + boolean needNext (); + void onPasswordConfirmed (ViewController c, String password); + } + + private CustomConfirmDelegate confirmDelegate; + @Override public CharSequence getName () { switch (mode) { @@ -224,6 +242,9 @@ public CharSequence getName () { case MODE_UNLOCK_EDIT: { return Lang.getString(R.string.EnterPassword); } + case MODE_CUSTOM_CONFIRM: { + return confirmDelegate.getName(); + } case MODE_TRANSFER_OWNERSHIP_CONFIRM: { return Lang.getString(R.string.TransferOwnershipPasswordAlert); } @@ -290,6 +311,8 @@ private int getDoneIcon () { case MODE_CONFIRM: case MODE_TRANSFER_OWNERSHIP_CONFIRM: return R.drawable.baseline_check_24; + case MODE_CUSTOM_CONFIRM: + return confirmDelegate.needNext() ? R.drawable.baseline_arrow_forward_24 : R.drawable.baseline_check_24; } return R.drawable.baseline_arrow_forward_24; } @@ -368,6 +391,7 @@ protected View onCreateView (Context context) { break; } case MODE_TRANSFER_OWNERSHIP_CONFIRM: + case MODE_CUSTOM_CONFIRM: case MODE_CONFIRM: case MODE_UNLOCK_EDIT: { if (state != null && state.passwordHint != null && !state.passwordHint.isEmpty()) { @@ -438,11 +462,24 @@ protected View onCreateView (Context context) { switch (mode) { case MODE_TRANSFER_OWNERSHIP_CONFIRM: + case MODE_CUSTOM_CONFIRM: case MODE_CONFIRM: case MODE_UNLOCK_EDIT: case MODE_LOGIN: { updatePasswordResetTextViews(); - hint = Lang.getString(mode == MODE_TRANSFER_OWNERSHIP_CONFIRM ? R.string.TransferOwnershipPasswordAlertHint : R.string.LoginPasswordText); + @StringRes int res; + switch (mode) { + case MODE_TRANSFER_OWNERSHIP_CONFIRM: + res = R.string.TransferOwnershipPasswordAlertHint; + break; + case MODE_CUSTOM_CONFIRM: + res = R.string.ConfirmPasswordAlertHint; + break; + default: + res = R.string.LoginPasswordText; + break; + } + hint = Lang.getString(res); break; } case MODE_EMAIL_RECOVERY: @@ -480,7 +517,7 @@ protected View onCreateView (Context context) { } } - if (mode == MODE_TRANSFER_OWNERSHIP_CONFIRM || mode == MODE_UNLOCK_EDIT || mode == MODE_CONFIRM || mode == MODE_LOGIN || mode == MODE_CODE || mode == MODE_CODE_CHANGE || mode == MODE_CODE_PHONE_CONFIRM || mode == MODE_CODE_EMAIL) { + if (mode == MODE_TRANSFER_OWNERSHIP_CONFIRM || mode == MODE_UNLOCK_EDIT || mode == MODE_CUSTOM_CONFIRM || mode == MODE_CONFIRM || mode == MODE_LOGIN || mode == MODE_CODE || mode == MODE_CODE_CHANGE || mode == MODE_CODE_PHONE_CONFIRM || mode == MODE_CODE_EMAIL) { RelativeLayout forgotWrap = new RelativeLayout(context); forgotWrap.setLayoutParams(FrameLayoutFix.newParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM)); @@ -1037,7 +1074,7 @@ private void updatePasswordResetTextViews () { handler.sendMessageDelayed(Message.obtain(handler, UPDATE_TEXT_VIEWS_TIMER), 1000); } else { - forgotView.setText(Lang.getString(R.string.ForgotPassword)); + forgotView.setText(Lang.getString(canResetPassword() ? R.string.ResetPassword : R.string.ForgotPassword)); resetWaitView.setVisibility(View.GONE); } } @@ -1300,6 +1337,8 @@ private void unlockEdit (final String password) { } else if ((mode == MODE_CONFIRM || mode == MODE_TRANSFER_OWNERSHIP_CONFIRM) && getArguments() != null && getArguments().onSuccessListener != null) { navigateBack(); getArgumentsStrict().onSuccessListener.runWithData(password); + } else if (mode == MODE_CUSTOM_CONFIRM) { + confirmDelegate.onPasswordConfirmed(this, password); } } })); @@ -1591,6 +1630,7 @@ private void proceed () { switch (mode) { case MODE_CONFIRM: case MODE_TRANSFER_OWNERSHIP_CONFIRM: + case MODE_CUSTOM_CONFIRM: case MODE_UNLOCK_EDIT: { if (!input.isEmpty()) { unlockEdit(input); @@ -1668,6 +1708,7 @@ private void proceedForgot () { } case MODE_CONFIRM: case MODE_TRANSFER_OWNERSHIP_CONFIRM: + case MODE_CUSTOM_CONFIRM: case MODE_UNLOCK_EDIT: case MODE_LOGIN: { requestRecovery(); diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsLogOutController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsLogOutController.java index 3d080068d2..6577495c20 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsLogOutController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsLogOutController.java @@ -17,19 +17,36 @@ import android.content.Context; import android.view.View; +import androidx.annotation.IdRes; +import androidx.annotation.IntDef; + import org.thunderdog.challegram.R; import org.thunderdog.challegram.component.base.SettingView; import org.thunderdog.challegram.core.Lang; +import org.thunderdog.challegram.data.TGFoundChat; import org.thunderdog.challegram.navigation.DoubleHeaderView; import org.thunderdog.challegram.telegram.Tdlib; import org.thunderdog.challegram.theme.ColorId; +import org.thunderdog.challegram.tool.Strings; import org.thunderdog.challegram.unsorted.Passcode; import org.thunderdog.challegram.v.CustomRecyclerView; +import org.thunderdog.challegram.widget.BetterChatView; import java.util.ArrayList; import java.util.List; -public class SettingsLogOutController extends RecyclerViewController implements View.OnClickListener { +public class SettingsLogOutController extends RecyclerViewController implements View.OnClickListener { + @IntDef({ + Type.LOG_OUT, Type.DELETE_ACCOUNT + }) + public @interface Type { + int LOG_OUT = 0, DELETE_ACCOUNT = 1; + } + + private @Type int getType () { + return getArguments() == null ? Type.LOG_OUT : getArgumentsStrict(); + } + public SettingsLogOutController (Context context, Tdlib tdlib) { super(context, tdlib); } @@ -41,7 +58,13 @@ public int getId () { @Override public CharSequence getName () { - return Lang.getString(R.string.LogOut); + switch (getType()) { + case Type.LOG_OUT: + return Lang.getString(R.string.LogOut); + case Type.DELETE_ACCOUNT: + return Lang.getString(R.string.DeleteAccount); + } + throw new UnsupportedOperationException(); } private SettingsAdapter adapter; @@ -70,12 +93,25 @@ protected void onCreateView (Context context, CustomRecyclerView recyclerView) { adapter = new SettingsAdapter(this) { @Override protected void setValuedSetting (ListItem item, SettingView view, boolean isUpdate) { - view.setIconColorId(item.getId() == R.id.btn_logout ? ColorId.iconNegative : ColorId.NONE); + @IdRes int itemId = item.getId(); + view.setIconColorId(itemId == R.id.btn_logout || itemId == R.id.btn_deleteAccount ? ColorId.iconNegative : ColorId.NONE); + } + + @Override + protected void setChatData (ListItem item, int position, BetterChatView chatView) { + chatView.setEnabled(false); + chatView.setChat((TGFoundChat) item.getData()); } }; List items = new ArrayList<>(); + @Type int type = getType(); + + TGFoundChat chat = new TGFoundChat(tdlib, tdlib.mySender(), true); + chat.setForcedSubtitle(Strings.formatPhone(tdlib.account().getPhoneNumber())); + items.add(new ListItem(ListItem.TYPE_CHAT_BETTER).setData(chat)); + items.add(new ListItem(ListItem.TYPE_SEPARATOR)); items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_addAccount, R.drawable.baseline_person_add_24, R.string.SignOutAltAddAccount)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.SignOutAltAddAccountHint)); @@ -100,12 +136,19 @@ protected void setValuedSetting (ListItem item, SettingView view, boolean isUpda items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_help, R.drawable.baseline_help_24, R.string.SignOutAltHelp)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); - items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.SignOutAltHelpHint)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, type == Type.DELETE_ACCOUNT ? R.string.DeleteAccountHelpHint : R.string.SignOutAltHelpHint)); items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); - items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_logout, R.drawable.baseline_delete_forever_24, R.string.LogOut).setTextColorId(ColorId.textNegative)); + items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_logout, R.drawable.baseline_logout_24, R.string.LogOut).setTextColorId(ColorId.textNegative)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); - items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.SignOutAltHint2)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, type == Type.DELETE_ACCOUNT ? R.string.DeleteAccountSignOutAltHint2 : R.string.SignOutAltHint2)); + + if (type == Type.DELETE_ACCOUNT) { + items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); + items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_deleteAccount, R.drawable.baseline_delete_alert_24, R.string.DeleteAccountBtn).setTextColorId(ColorId.textNegative)); + items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.DeleteAccountInfo)); + } adapter.setItems(items, false); @@ -129,6 +172,8 @@ public void onClick (View v) { tdlib.ui().openSupport(this); } else if (viewId == R.id.btn_logout) { tdlib.ui().logOut(this, false); + } else if (viewId == R.id.btn_deleteAccount) { + tdlib.ui().permanentlyDeleteAccount(this, false); } } } diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java index c016b04722..e6559ded4d 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsPrivacyController.java @@ -222,6 +222,11 @@ public void setValuedSetting (ListItem item, SettingView v, boolean isUpdate) { items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_accountTTL, 0, R.string.DeleteAccountIfAwayFor2)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.DeleteAccountHelp)); + + items.add(new ListItem(ListItem.TYPE_SHADOW_TOP)); + items.add(new ListItem(ListItem.TYPE_SETTING, R.id.btn_deleteAccount, 0, R.string.DeleteMyAccount).setTextColorId(ColorId.textNegative)); + items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); + items.add(new ListItem(ListItem.TYPE_DESCRIPTION, 0, 0, R.string.DeleteMyAccountInfo)); } @@ -454,6 +459,8 @@ public void onClick (View v) { new ListItem(ListItem.TYPE_RADIO_OPTION, R.id.btn_6month, 0, Lang.pluralBold(R.string.xMonths, 6), R.id.btn_accountTTL, months == 6), new ListItem(ListItem.TYPE_RADIO_OPTION, R.id.btn_1year, 0, Lang.pluralBold(R.string.xYears, 1), R.id.btn_accountTTL, years == 1) }, this); + } else if (id == R.id.btn_deleteAccount) { + tdlib.ui().permanentlyDeleteAccount(this, true); } else if (id == R.id.btn_clearAllDrafts) { showOptions(Lang.getString(R.string.AreYouSureClearDrafts), new int[] {R.id.btn_clearAllDrafts, R.id.btn_cancel}, new String[] {Lang.getString(R.string.PrivacyDeleteCloudDrafts), Lang.getString(R.string.Cancel)}, new int[] {OPTION_COLOR_RED, OPTION_COLOR_NORMAL}, new int[] {R.drawable.baseline_delete_forever_24, R.drawable.baseline_cancel_24}, (itemView, actionId) -> { if (actionId == R.id.btn_clearAllDrafts) { diff --git a/app/src/main/res/drawable/baseline_delete_alert_24.xml b/app/src/main/res/drawable/baseline_delete_alert_24.xml new file mode 100644 index 0000000000..fdf181ef77 --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_alert_24.xml @@ -0,0 +1,8 @@ + + + \ + \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_logout_24.xml b/app/src/main/res/drawable/baseline_logout_24.xml new file mode 100644 index 0000000000..52076a032c --- /dev/null +++ b/app/src/main/res/drawable/baseline_logout_24.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index fe725e01a7..d0937a211d 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -138,6 +138,7 @@ + @@ -369,6 +370,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0d943d600f..4450d59b54 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -510,6 +510,11 @@ Please allow Telegram X to use camera in System Settings > Apps > Telegram X > Permissions Settings Log out + Delete account + Permanently delete account + Access to your chats will be lost forever. All existing chats will see you as Deleted Account. Using the same phone number will create a new account. + Delete my account now + Permanently delete the account, and all associated information from Telegram servers. Mute Unmute Return to Group @@ -1696,7 +1701,7 @@ Note: you will be able to download these files any time later. Freed %1$s of disk space Link Preview - Are you sure you want to clear history for this channell? This action cannot be undone. + Are you sure you want to clear history for this channel? This action cannot be undone. **No, seriously.**\n\nThis will delete **all messages** for **all subscribers**. There will be no way to restore them. Are you sure you want to clear history for this chat for all users? This action cannot be undone. Are you sure you want to clear history for this chat? This action cannot be undone. @@ -2824,6 +2829,8 @@ Contact Support Tell us about any issues; logging out doesn\'t usually help. Remember, logging out kills all your Secret Chats. Downloaded media will be erased from this device. + Sign out without deleting the account. You can sign back in using the same phone number to access your chats. + Tell us about any issues; after you delete the account, we won\'t be able to restore any data you lose in the process. Push Services @@ -4954,4 +4961,16 @@ This is a service message from Telegram This message is unsupported in the installed version of Telegram X. + Please enter your 2-Step verification password to confirm the action. + Reason + **Note:** you can simultaneously use your Telegram account on as many devices as you want, and change the phone number without starting over.\n\nYou should use this option if you want to erase all data associated with your account on Telegram servers forever. + **Wait. You are entering danger zone.**\n\nThis action will permanently erase all data associated with your account on Telegram servers. There will be no way to restore it.\n\nAccess to any channels or groups, where you are the only admin, **will be lost forever**. + Show alternatives + **No, seriously.**\n\nThis will permanently delete all data associated with your account. There will be no way to restore it.\n\nAccess to any channels or groups, where you are the only admin, **will be lost forever**. + Proceed anyway + **Danger:** this is the last confirmation prompt.\n\nOnce you press the button below, all data will be erased from Telegram servers. Using the same phone number will create a new empty account. + Alright, delete account. + Account deleted + Your account %1$s (%2$s) is now deleted from Telegram servers, and can no longer be restored.\n\nUsing the same phone number will create a new account. + From 5f5bca3acbde4f2c5e277a7fd043e0bd07fcc98b Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 00:21:17 +0400 Subject: [PATCH 14/23] Sync `strings.xml` with `translations.telegram.org` --- app/src/main/res/values/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4450d59b54..4c3710eae3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4895,11 +4895,11 @@ Are you sure you want to remove "%1$s" from the always excluded list? Are you sure you want to remove "%1$s" from the always excluded list? Include Chats - You can select only one non-secret chat and only one secret chat. - You can select up to %1$s non-secret chats and the same number of secret chats. + You can select only one cloud and only one secret chat. + You can select up to %1$s cloud chats and the same number of secret chats. Exclude Chats - You can select only one non-secret chat and only one secret chat - You can select up to %1$s non-secret chats and the same number of secret chats. + You can select only one cloud chat and only one secret chat + You can select up to %1$s cloud chats and the same number of secret chats. Included Chats Choose chats or types of chats that will appear in this folder. Excluded Chats From 61bcb9bf5dbc599d771dccf2c70d3db3d89af10a Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 08:14:14 +0400 Subject: [PATCH 15/23] Version bump to `1670` --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 94c91da128..fe4c85122b 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ # App -version.app=1669 +version.app=1670 version.major=0 # Anchor date point in app versioning version.creation=873642600564 From aa9985a7cce56045b4e757e6e2ced759b5d9eaa1 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 08:20:39 +0400 Subject: [PATCH 16/23] Fixed showing the last confirmation screen when 2FA is enabled --- .../main/java/org/thunderdog/challegram/telegram/TdlibUi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java index 018b94b5b7..f01de521a5 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibUi.java @@ -3773,7 +3773,7 @@ public boolean needNext () { public void onPasswordConfirmed (ViewController c, String password) { EditDeleteAccountReasonController target = new EditDeleteAccountReasonController(context.context(), tdlib); target.setArguments(password); - context.navigateTo(target); + c.navigateTo(target); } }); }); From 4dbccfe8e798f176caeb073515dd319aa4d6d356 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 12:20:24 +0400 Subject: [PATCH 17/23] Version bump to `1671` --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index fe4c85122b..1e9dceb6ce 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ # App -version.app=1670 +version.app=1671 version.major=0 # Anchor date point in app versioning version.creation=873642600564 From 55f98a9f1c047962a73727d079584596cca791d5 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Mon, 25 Dec 2023 16:20:46 +0400 Subject: [PATCH 18/23] Internal support for `DeviceTokenHuaweiPush` and `DeviceTokenSimplePush` --- .../GoogleLocationRetriever.java | 4 +- .../LocationRetrieverFactory.java | 3 +- .../{ => push}/FirebaseTokenRetriever.java | 24 +++++- .../{ => push}/TokenRetrieverFactory.java | 4 +- .../service/FirebaseListenerService.java | 2 +- .../challegram/ui/MapControllerFactory.java | 2 +- .../challegram/ui/MapGoogleController.java | 2 +- .../thunderdog/challegram/telegram/Tdlib.java | 25 ++++--- .../telegram/TdlibNotificationUtils.java | 19 ++++- .../telegram/TdlibSettingsManager.java | 53 +++++-------- .../challegram/ui/SettingsBugController.java | 48 +++++++++--- .../ui/SettingsNotificationController.java | 15 ++-- .../challegram/unsorted/Settings.java | 74 ++++++++++++------- .../challegram/util/DeviceTokenType.java | 24 +++++- .../challegram/util/TokenRetriever.java | 3 + 15 files changed, 198 insertions(+), 104 deletions(-) rename app/src/google/java/org/thunderdog/challegram/{ => location}/GoogleLocationRetriever.java (97%) rename app/src/google/java/org/thunderdog/challegram/{ => location}/LocationRetrieverFactory.java (89%) rename app/src/google/java/org/thunderdog/challegram/{ => push}/FirebaseTokenRetriever.java (81%) rename app/src/google/java/org/thunderdog/challegram/{ => push}/TokenRetrieverFactory.java (89%) diff --git a/app/src/google/java/org/thunderdog/challegram/GoogleLocationRetriever.java b/app/src/google/java/org/thunderdog/challegram/location/GoogleLocationRetriever.java similarity index 97% rename from app/src/google/java/org/thunderdog/challegram/GoogleLocationRetriever.java rename to app/src/google/java/org/thunderdog/challegram/location/GoogleLocationRetriever.java index 4db87177a0..b28c43a31f 100644 --- a/app/src/google/java/org/thunderdog/challegram/GoogleLocationRetriever.java +++ b/app/src/google/java/org/thunderdog/challegram/location/GoogleLocationRetriever.java @@ -12,7 +12,7 @@ * * File created on 22/10/2022 */ -package org.thunderdog.challegram; +package org.thunderdog.challegram.location; import android.location.Location; @@ -26,6 +26,8 @@ import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStatusCodes; +import org.thunderdog.challegram.BaseActivity; +import org.thunderdog.challegram.Log; import org.thunderdog.challegram.tool.Intents; import org.thunderdog.challegram.tool.UI; import org.thunderdog.challegram.unsorted.LocationRetriever; diff --git a/app/src/google/java/org/thunderdog/challegram/LocationRetrieverFactory.java b/app/src/google/java/org/thunderdog/challegram/location/LocationRetrieverFactory.java similarity index 89% rename from app/src/google/java/org/thunderdog/challegram/LocationRetrieverFactory.java rename to app/src/google/java/org/thunderdog/challegram/location/LocationRetrieverFactory.java index 3266da1e07..27eb57e31c 100644 --- a/app/src/google/java/org/thunderdog/challegram/LocationRetrieverFactory.java +++ b/app/src/google/java/org/thunderdog/challegram/location/LocationRetrieverFactory.java @@ -12,8 +12,9 @@ * * File created on 22/10/2022 */ -package org.thunderdog.challegram; +package org.thunderdog.challegram.location; +import org.thunderdog.challegram.BaseActivity; import org.thunderdog.challegram.unsorted.LocationRetriever; public class LocationRetrieverFactory { diff --git a/app/src/google/java/org/thunderdog/challegram/FirebaseTokenRetriever.java b/app/src/google/java/org/thunderdog/challegram/push/FirebaseTokenRetriever.java similarity index 81% rename from app/src/google/java/org/thunderdog/challegram/FirebaseTokenRetriever.java rename to app/src/google/java/org/thunderdog/challegram/push/FirebaseTokenRetriever.java index d0764ad5ff..7a44844aeb 100644 --- a/app/src/google/java/org/thunderdog/challegram/FirebaseTokenRetriever.java +++ b/app/src/google/java/org/thunderdog/challegram/push/FirebaseTokenRetriever.java @@ -12,14 +12,21 @@ * * File created on 22/10/2022 */ -package org.thunderdog.challegram; +package org.thunderdog.challegram.push; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; import com.google.firebase.messaging.FirebaseMessaging; import org.drinkless.tdlib.TdApi; +import org.thunderdog.challegram.Log; +import org.thunderdog.challegram.TDLib; +import org.thunderdog.challegram.tool.UI; import org.thunderdog.challegram.util.TokenRetriever; import java.util.regex.Matcher; @@ -27,7 +34,7 @@ import me.vkryl.core.StringUtils; -public final class FirebaseTokenRetriever extends TokenRetriever { +final class FirebaseTokenRetriever extends TokenRetriever { @Override protected boolean performInitialization (Context context) { try { @@ -44,6 +51,19 @@ protected boolean performInitialization (Context context) { return false; } + @NonNull + @Override + public String getName () { + return "firebase"; + } + + @Override + @Nullable + public String getConfiguration () { + FirebaseOptions options = FirebaseOptions.fromResource(UI.getAppContext()); + return options != null ? options.toString() : null; + } + private static String extractFirebaseErrorName (Throwable e) { String message = e.getMessage(); if (!StringUtils.isEmpty(message)) { diff --git a/app/src/google/java/org/thunderdog/challegram/TokenRetrieverFactory.java b/app/src/google/java/org/thunderdog/challegram/push/TokenRetrieverFactory.java similarity index 89% rename from app/src/google/java/org/thunderdog/challegram/TokenRetrieverFactory.java rename to app/src/google/java/org/thunderdog/challegram/push/TokenRetrieverFactory.java index 81518abaf9..bcbd61ed88 100644 --- a/app/src/google/java/org/thunderdog/challegram/TokenRetrieverFactory.java +++ b/app/src/google/java/org/thunderdog/challegram/push/TokenRetrieverFactory.java @@ -12,13 +12,13 @@ * * File created on 22/10/2022 */ -package org.thunderdog.challegram; +package org.thunderdog.challegram.push; import android.content.Context; import org.thunderdog.challegram.util.TokenRetriever; -public class TokenRetrieverFactory { +public final class TokenRetrieverFactory { public static TokenRetriever newRetriever (Context context) { return new FirebaseTokenRetriever(); } diff --git a/app/src/google/java/org/thunderdog/challegram/service/FirebaseListenerService.java b/app/src/google/java/org/thunderdog/challegram/service/FirebaseListenerService.java index a1555411e0..4134dd0cc1 100644 --- a/app/src/google/java/org/thunderdog/challegram/service/FirebaseListenerService.java +++ b/app/src/google/java/org/thunderdog/challegram/service/FirebaseListenerService.java @@ -34,7 +34,7 @@ import me.vkryl.td.JSON; -public class FirebaseListenerService extends FirebaseMessagingService { +public final class FirebaseListenerService extends FirebaseMessagingService { @Override public void onNewToken (@NonNull String newToken) { UI.initApp(getApplicationContext()); diff --git a/app/src/google/java/org/thunderdog/challegram/ui/MapControllerFactory.java b/app/src/google/java/org/thunderdog/challegram/ui/MapControllerFactory.java index 08ab0f3ad3..6865337c91 100644 --- a/app/src/google/java/org/thunderdog/challegram/ui/MapControllerFactory.java +++ b/app/src/google/java/org/thunderdog/challegram/ui/MapControllerFactory.java @@ -18,7 +18,7 @@ import org.thunderdog.challegram.telegram.Tdlib; -public class MapControllerFactory { +public final class MapControllerFactory { public static MapController newMapController (Context context, Tdlib tdlib) { return new MapGoogleController(context, tdlib); } diff --git a/app/src/google/java/org/thunderdog/challegram/ui/MapGoogleController.java b/app/src/google/java/org/thunderdog/challegram/ui/MapGoogleController.java index 53c69bfcbf..dd255e3bf7 100644 --- a/app/src/google/java/org/thunderdog/challegram/ui/MapGoogleController.java +++ b/app/src/google/java/org/thunderdog/challegram/ui/MapGoogleController.java @@ -66,7 +66,7 @@ import me.vkryl.core.lambda.Destroyable; import me.vkryl.td.MessageId; -public class MapGoogleController extends MapController implements OnMapReadyCallback, GoogleMap.OnMyLocationChangeListener, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnMarkerClickListener { +final class MapGoogleController extends MapController implements OnMapReadyCallback, GoogleMap.OnMyLocationChangeListener, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnMarkerClickListener { private static final float DEFAULT_ZOOM_LEVEL = 16.0f; private static final float CLICK_ZOOM_LEVEL = 17.0f; diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java index 65ea839773..1a20ccb87f 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java @@ -5757,31 +5757,38 @@ private Map newConnectionParams () { if (deviceToken != null && (state == TdlibManager.TokenState.NONE || state == TdlibManager.TokenState.INITIALIZING)) { state = TdlibManager.TokenState.OK; } + String tokenProvider = TdlibNotificationUtils.getTokenRetriever().getName(); String error = context().getTokenError(); switch (state) { case TdlibManager.TokenState.ERROR: { - params.put("device_token", "FIREBASE_ERROR"); + params.put("device_token", tokenProvider.toUpperCase() + "_ERROR"); if (!StringUtils.isEmpty(error)) { - params.put("firebase_error", error); + params.put(tokenProvider + "_error", error); } break; } case TdlibManager.TokenState.INITIALIZING: { - params.put("device_token", "FIREBASE_INITIALIZING"); + params.put("device_token", tokenProvider.toUpperCase() + "_INITIALIZING"); break; } case TdlibManager.TokenState.OK: { + String tokenOrEndpoint; switch (deviceToken.getConstructor()) { - // TODO more push services - case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: { - String token = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; - params.put("device_token", token); + case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: + tokenOrEndpoint = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; + break; + case TdApi.DeviceTokenHuaweiPush.CONSTRUCTOR: + tokenOrEndpoint = ((TdApi.DeviceTokenHuaweiPush) deviceToken).token; + break; + case TdApi.DeviceTokenSimplePush.CONSTRUCTOR: + tokenOrEndpoint = ((TdApi.DeviceTokenSimplePush) deviceToken).endpoint; break; - } default: { - throw new UnsupportedOperationException(deviceToken.toString()); + Td.assertDeviceToken_de4a4f61(); + throw Td.unsupported(deviceToken); } } + params.put("device_token", tokenOrEndpoint); break; } case TdlibManager.TokenState.NONE: diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationUtils.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationUtils.java index 0ce45b1ffb..28f3ed431d 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationUtils.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationUtils.java @@ -32,7 +32,6 @@ import org.thunderdog.challegram.Log; import org.thunderdog.challegram.R; import org.thunderdog.challegram.TDLib; -import org.thunderdog.challegram.TokenRetrieverFactory; import org.thunderdog.challegram.U; import org.thunderdog.challegram.config.Config; import org.thunderdog.challegram.config.Device; @@ -40,6 +39,7 @@ import org.thunderdog.challegram.loader.ImageCache; import org.thunderdog.challegram.loader.ImageFile; import org.thunderdog.challegram.loader.ImageReader; +import org.thunderdog.challegram.push.TokenRetrieverFactory; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.Theme; import org.thunderdog.challegram.theme.ThemeId; @@ -53,6 +53,8 @@ import org.thunderdog.challegram.util.TokenRetriever; import org.thunderdog.challegram.util.text.Letters; +import me.vkryl.td.Td; + public class TdlibNotificationUtils { private static TextPaint lettersPaint; private static TextPaint lettersPaintFake; @@ -252,14 +254,25 @@ public static synchronized boolean initialize () { return tokenRetriever.initialize(UI.getAppContext()); } + public static @NonNull TokenRetriever getTokenRetriever () { + if (tokenRetriever == null) { + initialize(); + } + return tokenRetriever; + } + @DeviceTokenType public static int getDeviceTokenType (TdApi.DeviceToken deviceToken) { switch (deviceToken.getConstructor()) { - // TODO more push services case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: return DeviceTokenType.FIREBASE_CLOUD_MESSAGING; + case TdApi.DeviceTokenHuaweiPush.CONSTRUCTOR: + return DeviceTokenType.HUAWEI_PUSH_SERVICE; + case TdApi.DeviceTokenSimplePush.CONSTRUCTOR: + return DeviceTokenType.SIMPLE_PUSH_SERVICE; default: - throw new UnsupportedOperationException(deviceToken.toString()); + Td.assertDeviceToken_de4a4f61(); + throw Td.unsupported(deviceToken); } } diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibSettingsManager.java b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibSettingsManager.java index 719fb5378e..798c833c26 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/TdlibSettingsManager.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/TdlibSettingsManager.java @@ -72,7 +72,7 @@ public class TdlibSettingsManager implements CleanupStartupDelegate { private static final String NOTIFICATION_DATA_PREFIX = "notification_data_"; public static final String CONVERSION_PREFIX = "pending_conversion_"; - public static final String DEVICE_TOKEN_KEY = "registered_device_token"; + public static final String DEVICE_TOKEN_OR_ENDPOINT_KEY = "registered_device_token"; public static final String DEVICE_TOKEN_TYPE_KEY = "registered_device_token_type"; public static final String DEVICE_UID_KEY = "registered_device_uid"; public static final String DEVICE_OTHER_UID_KEY = "registered_device_uid_other"; @@ -616,49 +616,32 @@ private static String getRegisteredDeviceTdlibVersion2 (int accountId) { @Nullable private static TdApi.DeviceToken getRegisteredDeviceToken (int accountId) { @DeviceTokenType int tokenType = Settings.instance().getInt(key(DEVICE_TOKEN_TYPE_KEY, accountId), DeviceTokenType.FIREBASE_CLOUD_MESSAGING); - switch (tokenType) { - case DeviceTokenType.FIREBASE_CLOUD_MESSAGING: - default: { - String token = Settings.instance().getString(key(DEVICE_TOKEN_KEY, accountId), null); - if (!StringUtils.isEmpty(token)) { - return new TdApi.DeviceTokenFirebaseCloudMessaging(token, true); - } - break; - } - } - return null; + String tokenOrEndpoint = Settings.instance().getString(key(DEVICE_TOKEN_OR_ENDPOINT_KEY, accountId), null); + return Settings.newDeviceToken(tokenType, tokenOrEndpoint); } private static long[] getRegisteredDeviceOtherUserIds (int accountId) { return Settings.instance().pmc().getLongArray(key(DEVICE_OTHER_UID_KEY, accountId)); } - public static void setRegisteredDevice (int accountId, long userId, TdApi.DeviceToken deviceToken, @Nullable long[] otherUserIds) { + public static void setRegisteredDevice (int accountId, long userId, @Nullable TdApi.DeviceToken deviceToken, @Nullable long[] otherUserIds) { if (deviceToken == null) { unregisterDevice(accountId); + return; + } + LevelDB pmc = Settings.instance().edit(); + Settings.storeDeviceToken(deviceToken, pmc, + key(DEVICE_TOKEN_TYPE_KEY, accountId), + key(DEVICE_TOKEN_OR_ENDPOINT_KEY, accountId) + ); + pmc.putLong(key(DEVICE_UID_KEY, accountId), userId); + pmc.putString(key(DEVICE_TDLIB_VERSION2_KEY, accountId), BuildConfig.TDLIB_VERSION); + if (otherUserIds != null && otherUserIds.length > 0) { + pmc.putLongArray(key(DEVICE_OTHER_UID_KEY, accountId), otherUserIds); } else { - int tokenType = TdlibNotificationUtils.getDeviceTokenType(deviceToken); - LevelDB pmc = Settings.instance().edit(); - switch (deviceToken.getConstructor()) { - case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: { - String token = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; - pmc.putInt(key(DEVICE_TOKEN_TYPE_KEY, accountId), tokenType) - .putString(key(DEVICE_TOKEN_KEY, accountId), token); - break; - } - default: { - throw new UnsupportedOperationException(deviceToken.toString()); - } - } - pmc.putLong(key(DEVICE_UID_KEY, accountId), userId); - pmc.putString(key(DEVICE_TDLIB_VERSION2_KEY, accountId), BuildConfig.TDLIB_VERSION); - if (otherUserIds != null && otherUserIds.length > 0) { - pmc.putLongArray(key(DEVICE_OTHER_UID_KEY, accountId), otherUserIds); - } else { - pmc.remove(key(DEVICE_OTHER_UID_KEY, accountId)); - } - pmc.apply(); + pmc.remove(key(DEVICE_OTHER_UID_KEY, accountId)); } + pmc.apply(); } public static boolean checkRegisteredDeviceToken (int accountId, long userId, TdApi.DeviceToken token, long[] otherUserIds, boolean skipOtherUserIdsCheck) { @@ -671,7 +654,7 @@ public static boolean checkRegisteredDeviceToken (int accountId, long userId, Td public static void unregisterDevice (int accountId) { Settings.instance().edit() - .remove(key(DEVICE_TOKEN_KEY, accountId)) + .remove(key(DEVICE_TOKEN_OR_ENDPOINT_KEY, accountId)) .remove(key(DEVICE_TOKEN_TYPE_KEY, accountId)) .remove(key(DEVICE_UID_KEY, accountId)) .remove(key(DEVICE_OTHER_UID_KEY, accountId)) diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBugController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBugController.java index 85318cdb96..a99a8dac01 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsBugController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsBugController.java @@ -26,8 +26,6 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import com.google.firebase.FirebaseOptions; - import org.drinkless.tdlib.TdApi; import org.drinkmore.Tracer; import org.thunderdog.challegram.BuildConfig; @@ -49,6 +47,7 @@ import org.thunderdog.challegram.telegram.Tdlib; import org.thunderdog.challegram.telegram.TdlibAccount; import org.thunderdog.challegram.telegram.TdlibManager; +import org.thunderdog.challegram.telegram.TdlibNotificationUtils; import org.thunderdog.challegram.telegram.TdlibUi; import org.thunderdog.challegram.theme.ColorId; import org.thunderdog.challegram.theme.PorterDuffColorId; @@ -83,6 +82,7 @@ import me.vkryl.core.lambda.RunnableBool; import me.vkryl.core.unit.ByteUnit; import me.vkryl.td.ChatPosition; +import me.vkryl.td.Td; public class SettingsBugController extends RecyclerViewController implements View.OnClickListener, @@ -410,6 +410,30 @@ public View getCustomHeaderCell () { return customHeaderCell; } + private static String toHumanRepresentation (@Nullable TdApi.DeviceToken deviceToken) { + if (deviceToken == null) { + return "null"; + } + switch (deviceToken.getConstructor()) { + case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: { + TdApi.DeviceTokenFirebaseCloudMessaging fcm = (TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken; + return "Firebase: " + fcm.token + ", encrypt: " + fcm.encrypt; + } + case TdApi.DeviceTokenHuaweiPush.CONSTRUCTOR: { + TdApi.DeviceTokenHuaweiPush huaweiPush = (TdApi.DeviceTokenHuaweiPush) deviceToken; + return "Huawei: " + huaweiPush.token + ", encrypt: " + huaweiPush.encrypt; + } + case TdApi.DeviceTokenSimplePush.CONSTRUCTOR: { + TdApi.DeviceTokenSimplePush simplePush = (TdApi.DeviceTokenSimplePush) deviceToken; + return "Simple Push: " + simplePush.endpoint; + } + default: { + Td.assertDeviceToken_de4a4f61(); + throw Td.unsupported(deviceToken); + } + } + } + @Override protected void onCreateView (Context context, CustomRecyclerView recyclerView) { checkLogSize(false); @@ -483,17 +507,19 @@ protected void setValuedSetting (ListItem item, SettingView view, boolean isUpda case TdlibManager.TokenState.INITIALIZING: view.setData("Initializing..."); break; - case TdlibManager.TokenState.OK: - view.setData("Firebase: " + tdlib.context().getToken()); + case TdlibManager.TokenState.OK: { + TdApi.DeviceToken deviceToken = tdlib.context().getToken(); + view.setData(toHumanRepresentation(deviceToken)); break; + } case TdlibManager.TokenState.NONE: default: view.setData("Unknown"); break; } } else if (itemId == R.id.btn_secret_pushConfig) { - FirebaseOptions options = FirebaseOptions.fromResource(UI.getAppContext()); - view.setData(options != null ? options.toString() : "Unavailable"); + String configuration = TdlibNotificationUtils.getTokenRetriever().getConfiguration(); + view.setData(!StringUtils.isEmpty(configuration) ? configuration : "Unavailable"); } else if (itemId == R.id.btn_secret_appFingerprint) { view.setData(U.getApkFingerprint("SHA1")); } else if (itemId == R.id.btn_secret_pushStats) { @@ -702,7 +728,7 @@ protected void setValuedSetting (ListItem item, SettingView view, boolean isUpda items.add(new ListItem(ListItem.TYPE_SEPARATOR_FULL)); items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_secret_pushTtl, 0, "TTL", false)); items.add(new ListItem(ListItem.TYPE_SEPARATOR_FULL)); - items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_secret_pushConfig, 0, "App config", false)); + items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_secret_pushConfig, 0, "Configuration", false)); items.add(new ListItem(ListItem.TYPE_SEPARATOR_FULL)); items.add(new ListItem(ListItem.TYPE_VALUED_SETTING_COMPACT, R.id.btn_secret_appFingerprint, 0, "App fingerprint", false)); items.add(new ListItem(ListItem.TYPE_SHADOW_BOTTOM)); @@ -1129,12 +1155,12 @@ public void runWithBool (boolean arg) { } } else if (viewId == R.id.btn_secret_pushToken) { if (tdlib.context().getTokenState() == TdlibManager.TokenState.OK) { - UI.copyText("Firebase: " + tdlib.context().getToken(), R.string.CopiedText); + UI.copyText(toHumanRepresentation(tdlib.context().getToken()), R.string.CopiedText); } } else if (viewId == R.id.btn_secret_pushConfig) { - FirebaseOptions options = FirebaseOptions.fromResource(UI.getAppContext()); - if (options != null) { - UI.copyText("Firebase config: " + options, R.string.CopiedText); + String configuration = TdlibNotificationUtils.getTokenRetriever().getConfiguration(); + if (!StringUtils.isEmpty(configuration)) { + UI.copyText(configuration, R.string.CopiedText); } } else if (viewId == R.id.btn_secret_appFingerprint) { UI.copyText(U.getApkFingerprint("SHA1"), R.string.CopiedText); 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 8e11ae6e6e..f58a227cd6 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsNotificationController.java @@ -42,8 +42,6 @@ import androidx.collection.SparseArrayCompat; import androidx.recyclerview.widget.LinearLayoutManager; -import com.google.firebase.FirebaseOptions; - import org.drinkless.tdlib.TdApi; import org.thunderdog.challegram.Log; import org.thunderdog.challegram.R; @@ -65,6 +63,7 @@ import org.thunderdog.challegram.telegram.TdlibAccount; import org.thunderdog.challegram.telegram.TdlibNotificationChannelGroup; import org.thunderdog.challegram.telegram.TdlibNotificationManager; +import org.thunderdog.challegram.telegram.TdlibNotificationUtils; import org.thunderdog.challegram.telegram.TdlibOptionListener; import org.thunderdog.challegram.telegram.TdlibSettingsManager; import org.thunderdog.challegram.telegram.TdlibUi; @@ -76,6 +75,7 @@ import org.thunderdog.challegram.util.RingtoneItem; import org.thunderdog.challegram.util.SimpleStringItem; import org.thunderdog.challegram.util.StringList; +import org.thunderdog.challegram.util.TokenRetriever; import org.thunderdog.challegram.util.text.Text; import org.thunderdog.challegram.v.CustomRecyclerView; import org.thunderdog.challegram.widget.InfiniteRecyclerView; @@ -1370,16 +1370,17 @@ private void shareTokenError () { Throwable fullError = tdlib.context().getTokenFullError(); String error = tdlib.context().getTokenError(); if (!StringUtils.isEmpty(error) || fullError != null) { - String report = "#firebase_error"; + TokenRetriever retriever = TdlibNotificationUtils.getTokenRetriever(); + String report = "#" + retriever.getName() + "_error"; if (!StringUtils.isEmpty(error)) { report += " " + error; } report += "\n\n"; - FirebaseOptions firebaseOptions = FirebaseOptions.fromResource(UI.getAppContext()); - if (firebaseOptions != null) { - report += "Firebase options:\n" + firebaseOptions; + String configuration = retriever.getConfiguration(); + if (!StringUtils.isEmpty(configuration)) { + report += "Configuration:\n" + configuration; } else { - report += "Firebase options unavailable!"; + report += "Configuration unavailable!"; } report += "\n" + U.getUsefulMetadata(tdlib); if (fullError != null) { 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 3292d48320..fe9e77828a 100644 --- a/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java +++ b/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java @@ -321,7 +321,7 @@ public static String accountInfoPrefix (int accountId) { private static final @Deprecated String KEY_PUSH_USER_IDS = "push_user_ids"; private static final @Deprecated String KEY_PUSH_USER_ID = "push_user_id"; private static final String KEY_PUSH_DEVICE_TOKEN_TYPE = "push_device_token_type"; - private static final String KEY_PUSH_DEVICE_TOKEN = "push_device_token"; + private static final String KEY_PUSH_DEVICE_TOKEN_OR_ENDPOINT = "push_device_token"; private static final String KEY_PUSH_STATS_TOTAL_COUNT = "push_stats_total"; private static final String KEY_PUSH_STATS_CURRENT_APP_VERSION_COUNT = "push_stats_app"; private static final String KEY_PUSH_STATS_CURRENT_TOKEN_COUNT = "push_stats_token"; @@ -1844,7 +1844,7 @@ private void upgradePmc (LevelDB pmc, SharedPreferences.Editor editor, int versi case VERSION_24: { int accountNum = TdlibManager.readAccountNum(); for (int accountId = 0; accountId < accountNum; accountId++) { - editor.remove(TdlibSettingsManager.key(TdlibSettingsManager.DEVICE_TOKEN_KEY, accountId)); + editor.remove(TdlibSettingsManager.key(TdlibSettingsManager.DEVICE_TOKEN_OR_ENDPOINT_KEY, accountId)); editor.remove(TdlibSettingsManager.key(TdlibSettingsManager.DEVICE_UID_KEY, accountId)); editor.remove(TdlibSettingsManager.key(TdlibSettingsManager.DEVICE_OTHER_UID_KEY, accountId)); } @@ -6370,47 +6370,67 @@ public long getPeriodicSyncFrequencySeconds () { // Push token + public static void storeDeviceToken (@NonNull TdApi.DeviceToken deviceToken, SharedPreferences.Editor editor, final String keyTokenType, final String keyTokenOrEndpoint) { + @DeviceTokenType int tokenType = TdlibNotificationUtils.getDeviceTokenType(deviceToken); + final String tokenOrEndpoint; + switch (tokenType) { + case DeviceTokenType.FIREBASE_CLOUD_MESSAGING: + tokenOrEndpoint = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; + break; + case DeviceTokenType.HUAWEI_PUSH_SERVICE: + tokenOrEndpoint = ((TdApi.DeviceTokenHuaweiPush) deviceToken).token; + break; + case DeviceTokenType.SIMPLE_PUSH_SERVICE: + tokenOrEndpoint = ((TdApi.DeviceTokenSimplePush) deviceToken).endpoint; + break; + default: + Td.assertDeviceToken_de4a4f61(); + throw Td.unsupported(deviceToken); + } + editor + .putInt(keyTokenType, tokenType) + .putString(keyTokenOrEndpoint, tokenOrEndpoint); + } + public void setDeviceToken (TdApi.DeviceToken token) { if (token == null) { pmc.edit() .remove(KEY_PUSH_DEVICE_TOKEN_TYPE) - .remove(KEY_PUSH_DEVICE_TOKEN) + .remove(KEY_PUSH_DEVICE_TOKEN_OR_ENDPOINT) .apply(); } else if (!Td.equalsTo(token, getDeviceToken())) { resetTokenPushMessageCount(); - int tokenType = TdlibNotificationUtils.getDeviceTokenType(token); - switch (token.getConstructor()) { - case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: { - TdApi.DeviceTokenFirebaseCloudMessaging fcmToken = (TdApi.DeviceTokenFirebaseCloudMessaging) token; - pmc.edit() - .putInt(KEY_PUSH_DEVICE_TOKEN_TYPE, tokenType) - .putString(KEY_PUSH_DEVICE_TOKEN, fcmToken.token) - .apply(); - break; - } - default: { - throw new UnsupportedOperationException(token.toString()); - } - } + SharedPreferences.Editor editor = pmc.edit(); + Settings.storeDeviceToken(token, editor, + KEY_PUSH_DEVICE_TOKEN_TYPE, + KEY_PUSH_DEVICE_TOKEN_OR_ENDPOINT + ); + editor.apply(); } } - @Nullable - public TdApi.DeviceToken getDeviceToken () { - @DeviceTokenType int tokenType = pmc.getInt(KEY_PUSH_DEVICE_TOKEN_TYPE, DeviceTokenType.FIREBASE_CLOUD_MESSAGING); + public static TdApi.DeviceToken newDeviceToken (@DeviceTokenType int tokenType, @Nullable String tokenOrEndpoint) { + if (StringUtils.isEmpty(tokenOrEndpoint)) { + return null; + } switch (tokenType) { case DeviceTokenType.FIREBASE_CLOUD_MESSAGING: - default: { - String token = pmc.getString(KEY_PUSH_DEVICE_TOKEN, null); - if (!StringUtils.isEmpty(token)) { - return new TdApi.DeviceTokenFirebaseCloudMessaging(token, true); - } - break; - } + return new TdApi.DeviceTokenFirebaseCloudMessaging(tokenOrEndpoint, true); + case DeviceTokenType.SIMPLE_PUSH_SERVICE: + return new TdApi.DeviceTokenSimplePush(tokenOrEndpoint); + case DeviceTokenType.HUAWEI_PUSH_SERVICE: + return new TdApi.DeviceTokenHuaweiPush(tokenOrEndpoint, true); } return null; } + @Nullable + public TdApi.DeviceToken getDeviceToken () { + @DeviceTokenType int tokenType = pmc.getInt(KEY_PUSH_DEVICE_TOKEN_TYPE, DeviceTokenType.FIREBASE_CLOUD_MESSAGING); + String tokenOrEndpoint = pmc.getString(KEY_PUSH_DEVICE_TOKEN_OR_ENDPOINT, null); + return newDeviceToken(tokenType, tokenOrEndpoint); + } + // Device ID used to anonymously identify crashes from the same client private String crashDeviceId; diff --git a/app/src/main/java/org/thunderdog/challegram/util/DeviceTokenType.java b/app/src/main/java/org/thunderdog/challegram/util/DeviceTokenType.java index a6b3ea8234..01fe12fbb6 100644 --- a/app/src/main/java/org/thunderdog/challegram/util/DeviceTokenType.java +++ b/app/src/main/java/org/thunderdog/challegram/util/DeviceTokenType.java @@ -1,3 +1,17 @@ +/* + * This file is a part of Telegram X + * Copyright © 2014 (tgx-android@pm.me) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * File created on 01/06/2023 + */ package org.thunderdog.challegram.util; import androidx.annotation.IntDef; @@ -7,9 +21,13 @@ @Retention(RetentionPolicy.SOURCE) @IntDef({ - DeviceTokenType.FIREBASE_CLOUD_MESSAGING + DeviceTokenType.FIREBASE_CLOUD_MESSAGING, + DeviceTokenType.HUAWEI_PUSH_SERVICE, + DeviceTokenType.SIMPLE_PUSH_SERVICE }) public @interface DeviceTokenType { - // TODO more push services. When adding new types, check usages to confirm support in other places - int FIREBASE_CLOUD_MESSAGING = 0; + int + FIREBASE_CLOUD_MESSAGING = 0, + HUAWEI_PUSH_SERVICE = 1, + SIMPLE_PUSH_SERVICE = 2; } diff --git a/app/src/main/java/org/thunderdog/challegram/util/TokenRetriever.java b/app/src/main/java/org/thunderdog/challegram/util/TokenRetriever.java index 610bba9bd6..b5d0df4cd2 100644 --- a/app/src/main/java/org/thunderdog/challegram/util/TokenRetriever.java +++ b/app/src/main/java/org/thunderdog/challegram/util/TokenRetriever.java @@ -28,6 +28,9 @@ public final boolean initialize (Context context) { protected abstract boolean performInitialization (Context context); + public abstract @NonNull String getName (); + public abstract @Nullable String getConfiguration (); + public final void retrieveDeviceToken (int retryCount, RegisterCallback callback) { if (!isInitialized) { throw new IllegalStateException(); From f2b6602552825c9f51b46a88162d0a1e552d5fbb Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:14:02 +0400 Subject: [PATCH 19/23] Add `huawei://` prefix to device token provided by Huawei Push Service --- .../java/org/thunderdog/challegram/telegram/Tdlib.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java index 1a20ccb87f..884d8d647e 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java @@ -87,6 +87,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.TimeZone; @@ -5773,13 +5774,18 @@ private Map newConnectionParams () { } case TdlibManager.TokenState.OK: { String tokenOrEndpoint; - switch (deviceToken.getConstructor()) { + switch (Objects.requireNonNull(deviceToken).getConstructor()) { case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: tokenOrEndpoint = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; break; - case TdApi.DeviceTokenHuaweiPush.CONSTRUCTOR: + case TdApi.DeviceTokenHuaweiPush.CONSTRUCTOR: { tokenOrEndpoint = ((TdApi.DeviceTokenHuaweiPush) deviceToken).token; + final String huaweiTokenPrefix = "huawei://"; + if (tokenOrEndpoint.startsWith(huaweiTokenPrefix)) { + tokenOrEndpoint = huaweiTokenPrefix + tokenOrEndpoint; + } break; + } case TdApi.DeviceTokenSimplePush.CONSTRUCTOR: tokenOrEndpoint = ((TdApi.DeviceTokenSimplePush) deviceToken).endpoint; break; From c8ccbae36d59943fcbe2962af37bac729c2cdc49 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:59:13 +0400 Subject: [PATCH 20/23] Upgraded TDLib to tdlib/td@5b8fff9 --- tdlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdlib b/tdlib index ea5e330d37..0f5853904a 160000 --- a/tdlib +++ b/tdlib @@ -1 +1 @@ -Subproject commit ea5e330d37ed6e01c7ccbb7fdb801913397e76ea +Subproject commit 0f5853904a5748207089d5d930b8caaaa499ed0a From acd8bedf8ab2a2215c56dc16852c285e5256d834 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Tue, 26 Dec 2023 16:53:52 +0400 Subject: [PATCH 21/23] Do not rely on `java.util.Objects` --- .../component/chat/ChatMembersSearcher.java | 4 ++-- .../chat/MessagesSearchManagerMiddleware.java | 6 ++--- .../popups/JoinRequestsComponent.java | 4 ++-- .../org/thunderdog/challegram/data/TD.java | 8 ++++--- .../challegram/data/ThreadInfo.java | 6 ++--- .../thunderdog/challegram/telegram/Tdlib.java | 4 ++-- .../ui/SettingsFoldersController.java | 24 +++++++++++++------ .../challegram/unsorted/Settings.java | 6 ++--- vkryl/android | 2 +- vkryl/core | 2 +- 10 files changed, 39 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/ChatMembersSearcher.java b/app/src/main/java/org/thunderdog/challegram/component/chat/ChatMembersSearcher.java index 340470a7ad..d4cc6553ed 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/ChatMembersSearcher.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/ChatMembersSearcher.java @@ -12,9 +12,9 @@ import org.thunderdog.challegram.util.text.Highlight; import java.util.ArrayList; -import java.util.Objects; import me.vkryl.core.ArrayUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.StringUtils; import me.vkryl.td.ChatId; import me.vkryl.td.Td; @@ -237,7 +237,7 @@ private void performRequestForUserChat (long userId, String query, Handler handl } private boolean checkContextId (String contextId) { - if (!Objects.equals(contextId, currentContextId)) { + if (!ObjectUtils.equals(contextId, currentContextId)) { currentContextId = contextId; reset(); return true; diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesSearchManagerMiddleware.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesSearchManagerMiddleware.java index 39a959b6de..829c3e3501 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesSearchManagerMiddleware.java +++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesSearchManagerMiddleware.java @@ -17,8 +17,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Objects; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.StringUtils; import me.vkryl.td.Td; @@ -354,7 +354,7 @@ public void search (TdApi.SearchSecretMessages query, @Nullable TdApi.MessageSen return; } - if (!Objects.equals(lastSecretNextOffset, query.offset)) { + if (!ObjectUtils.equals(lastSecretNextOffset, query.offset)) { TdApi.Error error = new TdApi.Error(400, "INCORRECT OFFSET"); UI.showError(error); resultHandler.onResult(error); @@ -461,7 +461,7 @@ public void dismiss () { } private boolean checkContextId (String contextId) { - if (!Objects.equals(contextId, currentContextId)) { + if (!ObjectUtils.equals(contextId, currentContextId)) { currentContextId = contextId; Log.i("SEARCH_MIDDLEWARE", "RESET"); reset(); diff --git a/app/src/main/java/org/thunderdog/challegram/component/popups/JoinRequestsComponent.java b/app/src/main/java/org/thunderdog/challegram/component/popups/JoinRequestsComponent.java index 4a2013733b..a48de836ad 100644 --- a/app/src/main/java/org/thunderdog/challegram/component/popups/JoinRequestsComponent.java +++ b/app/src/main/java/org/thunderdog/challegram/component/popups/JoinRequestsComponent.java @@ -43,11 +43,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; import me.vkryl.android.AnimatorUtils; import me.vkryl.core.ArrayUtils; +import me.vkryl.core.ObjectUtils; public class JoinRequestsComponent implements TGLegacyManager.EmojiLoadListener, Client.ResultHandler { private static final String UTYAN_EMOJI = "\uD83D\uDE0E"; @@ -364,7 +364,7 @@ public int getHeight (int predictUserCount) { } public void search (String query) { - if (Objects.equals(currentQuery, query)) { + if (ObjectUtils.equals(currentQuery, query)) { return; } diff --git a/app/src/main/java/org/thunderdog/challegram/data/TD.java b/app/src/main/java/org/thunderdog/challegram/data/TD.java index 763b68e506..8a7b11af25 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/TD.java +++ b/app/src/main/java/org/thunderdog/challegram/data/TD.java @@ -98,7 +98,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -109,6 +108,7 @@ import me.vkryl.core.ArrayUtils; import me.vkryl.core.DateUtils; import me.vkryl.core.FileUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.StringUtils; import me.vkryl.core.collection.IntList; import me.vkryl.core.collection.LongList; @@ -5126,8 +5126,10 @@ public static boolean contentEquals (TdApi.ChatFolder lhs, TdApi.ChatFolder rhs) if (lhs == rhs) { return true; } - return Objects.equals(lhs.title, rhs.title) && - Objects.equals(lhs.icon != null ? lhs.icon.name : null, rhs.icon != null ? rhs.icon.name : null) && + if (!ObjectUtils.equals(lhs.title, rhs.title)) return false; + String a = lhs.icon != null ? lhs.icon.name : null; + String b = rhs.icon != null ? rhs.icon.name : null; + return ObjectUtils.equals(a, b) && lhs.includeContacts == rhs.includeContacts && lhs.includeNonContacts == rhs.includeNonContacts && lhs.includeGroups == rhs.includeGroups && diff --git a/app/src/main/java/org/thunderdog/challegram/data/ThreadInfo.java b/app/src/main/java/org/thunderdog/challegram/data/ThreadInfo.java index 1bb7ef2bb7..5a754b9fd9 100644 --- a/app/src/main/java/org/thunderdog/challegram/data/ThreadInfo.java +++ b/app/src/main/java/org/thunderdog/challegram/data/ThreadInfo.java @@ -24,9 +24,8 @@ import org.thunderdog.challegram.telegram.MessageThreadListener; import org.thunderdog.challegram.telegram.Tdlib; -import java.util.Objects; - import me.vkryl.core.BitwiseUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.reference.ReferenceList; import me.vkryl.td.MessageId; import me.vkryl.td.Td; @@ -76,7 +75,8 @@ private ThreadInfo (@Nullable Tdlib tdlib, @NonNull TdApi.MessageThreadInfo thre } @Override public int hashCode () { - return Objects.hash(areComments(), contextChatId, threadInfo.chatId, threadInfo.messageThreadId); + Object[] objects = new Object[] {areComments(), contextChatId, threadInfo.chatId, threadInfo.messageThreadId}; + return ObjectUtils.hashCode(objects); } public boolean belongsTo (long chatId, long messageThreadId) { diff --git a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java index 884d8d647e..fac1274809 100644 --- a/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java +++ b/app/src/main/java/org/thunderdog/challegram/telegram/Tdlib.java @@ -87,7 +87,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Queue; import java.util.Set; import java.util.TimeZone; @@ -105,6 +104,7 @@ import me.vkryl.core.BitwiseUtils; import me.vkryl.core.FileUtils; import me.vkryl.core.MathUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.StringUtils; import me.vkryl.core.collection.LongList; import me.vkryl.core.collection.LongSet; @@ -5774,7 +5774,7 @@ private Map newConnectionParams () { } case TdlibManager.TokenState.OK: { String tokenOrEndpoint; - switch (Objects.requireNonNull(deviceToken).getConstructor()) { + switch (ObjectUtils.requireNonNull(deviceToken).getConstructor()) { case TdApi.DeviceTokenFirebaseCloudMessaging.CONSTRUCTOR: tokenOrEndpoint = ((TdApi.DeviceTokenFirebaseCloudMessaging) deviceToken).token; break; diff --git a/app/src/main/java/org/thunderdog/challegram/ui/SettingsFoldersController.java b/app/src/main/java/org/thunderdog/challegram/ui/SettingsFoldersController.java index fb46fb409c..829dc8a855 100644 --- a/app/src/main/java/org/thunderdog/challegram/ui/SettingsFoldersController.java +++ b/app/src/main/java/org/thunderdog/challegram/ui/SettingsFoldersController.java @@ -64,7 +64,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; import me.vkryl.android.widget.FrameLayoutFix; @@ -72,6 +71,7 @@ import me.vkryl.core.BitwiseUtils; import me.vkryl.core.ColorUtils; import me.vkryl.core.MathUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.collection.IntList; import me.vkryl.core.lambda.RunnableBool; import me.vkryl.td.ChatPosition; @@ -808,7 +808,9 @@ public boolean areItemsTheSame (ListItem oldItem, ListItem newItem) { @Override public boolean areContentsTheSame (ListItem oldItem, ListItem newItem) { - return Objects.equals(oldItem.getString(), newItem.getString()); + CharSequence a = oldItem.getString(); + CharSequence b = newItem.getString(); + return ObjectUtils.equals(a, b); } }; } @@ -819,7 +821,9 @@ private static DiffUtil.Callback recommendedChatFoldersDiff (List oldL public boolean areItemsTheSame (ListItem oldItem, ListItem newItem) { if (oldItem.getViewType() == newItem.getViewType() && oldItem.getId() == newItem.getId()) { if (oldItem.getId() == R.id.recommendedChatFolder) { - return Objects.equals(oldItem.getString(), newItem.getString()); + CharSequence a = oldItem.getString(); + CharSequence b = newItem.getString(); + return ObjectUtils.equals(a, b); } return true; } @@ -829,11 +833,17 @@ public boolean areItemsTheSame (ListItem oldItem, ListItem newItem) { @Override public boolean areContentsTheSame (ListItem oldItem, ListItem newItem) { if (oldItem.getId() == R.id.recommendedChatFolder) { - return oldItem.getIconResource() == newItem.getIconResource() && - Objects.equals(oldItem.getString(), newItem.getString()) && - Objects.equals(oldItem.getStringValue(), newItem.getStringValue()); + CharSequence a1 = oldItem.getString(); + CharSequence b1 = newItem.getString(); + if (oldItem.getIconResource() != newItem.getIconResource() || + !ObjectUtils.equals(a1, b1)) return false; + String a = oldItem.getStringValue(); + String b = newItem.getStringValue(); + return ObjectUtils.equals(a, b); } - return Objects.equals(oldItem.getString(), newItem.getString()); + CharSequence a = oldItem.getString(); + CharSequence b = newItem.getString(); + return ObjectUtils.equals(a, b); } }; } 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 fe9e77828a..fe84d0ed11 100644 --- a/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java +++ b/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java @@ -99,7 +99,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -108,6 +107,7 @@ import me.vkryl.core.ColorUtils; import me.vkryl.core.DateUtils; import me.vkryl.core.FileUtils; +import me.vkryl.core.ObjectUtils; import me.vkryl.core.StringUtils; import me.vkryl.core.lambda.CancellableRunnable; import me.vkryl.core.lambda.RunnableBool; @@ -3141,7 +3141,7 @@ public boolean isUnlimited () { @Override public int hashCode () { - return Objects.hash(majorSize, minorSize); + return ObjectUtils.hashCode(majorSize, minorSize); } } @@ -3187,7 +3187,7 @@ public boolean equals (Object o) { @Override public int hashCode () { - return Objects.hash(size, fps, bitrate); + return ObjectUtils.hashCode(size, fps, bitrate); } public VideoLimit () { diff --git a/vkryl/android b/vkryl/android index 5723143b71..cba4608f5b 160000 --- a/vkryl/android +++ b/vkryl/android @@ -1 +1 @@ -Subproject commit 5723143b710b0e85b4930e023a3ff9ac8d01a0bc +Subproject commit cba4608f5b7ba3556647f9fcc1d5bbb85dfa1377 diff --git a/vkryl/core b/vkryl/core index f0201029b1..c1c1fdb7d8 160000 --- a/vkryl/core +++ b/vkryl/core @@ -1 +1 @@ -Subproject commit f0201029b1f6fbdad0ae87f20e0a855c8dc1f07e +Subproject commit c1c1fdb7d8cab7e59808c3ef663c4f637d45369b From c4d4ffafbbec00dbdfe053366b80445beded63c2 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Thu, 28 Dec 2023 08:51:51 +0200 Subject: [PATCH 22/23] Updated `X-Core` submodule --- .../org/thunderdog/challegram/unsorted/Settings.java | 2 +- app/src/main/res/drawable/dotvhs_baseline_gift_24.xml | 9 +++++++++ vkryl/core | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/dotvhs_baseline_gift_24.xml 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 fe84d0ed11..a352a66344 100644 --- a/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java +++ b/app/src/main/java/org/thunderdog/challegram/unsorted/Settings.java @@ -3141,7 +3141,7 @@ public boolean isUnlimited () { @Override public int hashCode () { - return ObjectUtils.hashCode(majorSize, minorSize); + return ObjectUtils.hash(majorSize, minorSize); } } diff --git a/app/src/main/res/drawable/dotvhs_baseline_gift_24.xml b/app/src/main/res/drawable/dotvhs_baseline_gift_24.xml new file mode 100644 index 0000000000..8de3e49b8f --- /dev/null +++ b/app/src/main/res/drawable/dotvhs_baseline_gift_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/vkryl/core b/vkryl/core index c1c1fdb7d8..2c06947759 160000 --- a/vkryl/core +++ b/vkryl/core @@ -1 +1 @@ -Subproject commit c1c1fdb7d8cab7e59808c3ef663c4f637d45369b +Subproject commit 2c06947759e986a9ad24d805ad19d39aa0a30023 From 4c1efa82c4a98be6ba7da7246077a01c13ebd836 Mon Sep 17 00:00:00 2001 From: vkryl <6242627+vkryl@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:28:42 +0200 Subject: [PATCH 23/23] Version bump to `1672` --- version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.properties b/version.properties index 1e9dceb6ce..f2fff70c89 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ # App -version.app=1671 +version.app=1672 version.major=0 # Anchor date point in app versioning version.creation=873642600564